auklet 0.1.8 → 0.2.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,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;
@@ -11,19 +11,6 @@ const toBrowserVirtualPath = (id) => {
11
11
  const getRelativeFile = (file) => {
12
12
  return path.relative(process.cwd(), file);
13
13
  };
14
- const invalidateVirtualModules = (server, graph) => {
15
- const modules = [];
16
- for (const packageName of graph.getPackageNames()) {
17
- for (const entry of ['style.css', 'external.css', 'module.css']) {
18
- const module = server.moduleGraph.getModuleById(`\0auklet-css:${packageName}/${entry}`);
19
- if (!module)
20
- continue;
21
- server.moduleGraph.invalidateModule(module);
22
- modules.push(module);
23
- }
24
- }
25
- return modules;
26
- };
27
14
  const addVirtualStyleDependency = (virtualIdsByDependency, file, virtualId) => {
28
15
  const normalizedFile = normalizeFileKey(file);
29
16
  const values = virtualIdsByDependency.get(normalizedFile) ?? new Set();
@@ -39,6 +26,19 @@ const getDependencyVirtualModules = (virtualIdsByDependency, server, file) => {
39
26
  return module ? [module] : [];
40
27
  });
41
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
+ };
42
42
  export class AukletStyleHmr {
43
43
  graph;
44
44
  lastUpdateTimes = new Map();
@@ -72,35 +72,42 @@ export class AukletStyleHmr {
72
72
  !graph.isStyleFile(context.file)) {
73
73
  return;
74
74
  }
75
+ this.suppressFullReload();
76
+ graph.invalidateFile(context.file);
75
77
  if (this.isDuplicateUpdate(context.file)) {
76
78
  return [];
77
79
  }
78
- this.suppressFullReload();
79
- const virtualIds = getDependencyVirtualIds(this.virtualIdsByDependency, context.file);
80
- 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);
81
97
  for (const module of modules) {
82
- context.server.moduleGraph.invalidateModule(module);
98
+ server.moduleGraph.invalidateModule(module);
83
99
  }
84
- invalidateVirtualModules(context.server, graph);
85
- const updates = virtualIds.map((id) => {
86
- const browserPath = toBrowserVirtualPath(id);
87
- return {
88
- type: 'js-update',
89
- path: browserPath,
90
- acceptedPath: browserPath,
91
- timestamp: context.timestamp,
92
- explicitImportRequired: false,
93
- isWithinCircularImport: false,
94
- };
95
- });
96
- logger.info(`package css hmr ${getRelativeFile(context.file)} tracked=${virtualIds.length} updates=${updates.length}`);
100
+ const updates = createJsUpdates(virtualIds, timestamp);
97
101
  if (updates.length) {
98
- context.server.ws.send({
102
+ server.ws.send({
99
103
  type: 'update',
100
104
  updates,
101
105
  });
102
106
  }
103
- return [];
107
+ return {
108
+ sent: updates.length,
109
+ tracked: virtualIds.length,
110
+ };
104
111
  }
105
112
  suppressFullReload() {
106
113
  this.suppressFullReloadUntil = Date.now() + FULL_RELOAD_SUPPRESS_MS;
@@ -3,6 +3,7 @@ export declare class ModuleStyleGraph {
3
3
  private readonly config;
4
4
  private readonly packageSource;
5
5
  private readonly styleCodeFactory;
6
+ private readonly requestCache;
6
7
  private readonly loadAukletConfig;
7
8
  constructor(options: ModuleStyleGraphOptions);
8
9
  parsePackageStyleId(id: string): {
@@ -14,9 +15,9 @@ export declare class ModuleStyleGraph {
14
15
  isStyleFile(file: string): boolean;
15
16
  getPackageNames(): string[];
16
17
  getWatchRoots(): Promise<string[]>;
17
- createPackageStyleCode(parsed: PackageStyleId): Promise<{
18
- code: string;
19
- watchFiles: string[];
20
- }>;
21
- private createRequestCache;
18
+ createPackageStyleCode(parsed: PackageStyleId): Promise<import("#auklet/css/vite/moduleGraph/types").PackageStyleLoadResult>;
19
+ invalidatePackage(packageName: string): void;
20
+ invalidateFile(file: string): string | null;
21
+ isSourceModuleFile(file: string): boolean;
22
+ private isPackageFile;
22
23
  }
@@ -1,6 +1,7 @@
1
1
  import path from 'node:path';
2
2
  import { isAukletConfigFile } from '#auklet/config';
3
3
  import { loadAukletConfig } from '#auklet/configLoader';
4
+ import { SOURCE_MODULE_RE } from '#auklet/css/constants';
4
5
  import { moduleStyleBuildConfig } from '#auklet/css/config';
5
6
  import { parsePackageStyleId } from '#auklet/css/vite/moduleGraph/styleId';
6
7
  import { StyleCodeFactory } from '#auklet/css/vite/moduleGraph/styleCodeFactory';
@@ -13,6 +14,7 @@ export class ModuleStyleGraph {
13
14
  config;
14
15
  packageSource;
15
16
  styleCodeFactory;
17
+ requestCache;
16
18
  loadAukletConfig;
17
19
  constructor(options) {
18
20
  this.config = options.config ?? moduleStyleBuildConfig;
@@ -29,6 +31,13 @@ export class ModuleStyleGraph {
29
31
  styleExtensions: this.config.styleExtensions,
30
32
  loadAukletConfig: this.loadAukletConfig,
31
33
  });
34
+ this.requestCache = new ModuleStyleGraphRequestCache({
35
+ root: normalizeFileKey(options.root),
36
+ mode: options.mode ?? 'package',
37
+ packageSource: this.packageSource,
38
+ config: this.config,
39
+ loadAukletConfig: this.loadAukletConfig,
40
+ });
32
41
  }
33
42
  parsePackageStyleId(id) {
34
43
  return parsePackageStyleId(id, this.getPackageNames());
@@ -49,13 +58,29 @@ export class ModuleStyleGraph {
49
58
  return this.packageSource.getWatchRoots();
50
59
  }
51
60
  createPackageStyleCode(parsed) {
52
- return this.styleCodeFactory.createPackageStyleCode(parsed, this.createRequestCache());
61
+ return this.styleCodeFactory.createPackageStyleCode(parsed, this.requestCache);
53
62
  }
54
- createRequestCache() {
55
- return new ModuleStyleGraphRequestCache({
56
- packageSource: this.packageSource,
57
- config: this.config,
58
- loadAukletConfig: this.loadAukletConfig,
59
- });
63
+ invalidatePackage(packageName) {
64
+ this.requestCache.invalidatePackage(packageName);
65
+ }
66
+ invalidateFile(file) {
67
+ if (!this.isSourceGraphFile(file))
68
+ return null;
69
+ const normalizedFile = normalizeFileKey(file);
70
+ const stylePackage = this.packageSource
71
+ .getPackages()
72
+ .find((item) => this.isPackageFile(item.packageRoot, normalizedFile));
73
+ if (!stylePackage)
74
+ return null;
75
+ this.invalidatePackage(stylePackage.packageName);
76
+ return stylePackage.packageName;
77
+ }
78
+ isSourceModuleFile(file) {
79
+ return SOURCE_MODULE_RE.test(normalizeFileKey(file));
80
+ }
81
+ isPackageFile(packageRoot, file) {
82
+ const normalizedPackageRoot = normalizeFileKey(packageRoot);
83
+ return (file === normalizedPackageRoot ||
84
+ file.startsWith(`${normalizedPackageRoot}/`));
60
85
  }
61
86
  }
@@ -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,13 @@
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 readonly cacheRoot;
7
+ constructor(options: PersistentStyleGraphCacheOptions);
8
+ createKey(value: unknown): string;
9
+ read(key: string): PackageStyleLoadResult | null;
10
+ write(key: string, result: PackageStyleLoadResult, inputFiles: Array<string>): void;
11
+ private getCacheFile;
12
+ private isFresh;
13
+ }
@@ -0,0 +1,137 @@
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 stableStringify = (value) => {
7
+ if (Array.isArray(value)) {
8
+ return `[${value.map((item) => stableStringify(item)).join(',')}]`;
9
+ }
10
+ if (value && typeof value === 'object') {
11
+ return `{${Object.entries(value)
12
+ .sort(([left], [right]) => left.localeCompare(right))
13
+ .map(([key, item]) => `${JSON.stringify(key)}:${stableStringify(item)}`)
14
+ .join(',')}}`;
15
+ }
16
+ return JSON.stringify(value);
17
+ };
18
+ const hashValue = (value) => {
19
+ return crypto
20
+ .createHash('sha256')
21
+ .update(stableStringify(value))
22
+ .digest('hex');
23
+ };
24
+ const hashFile = (file) => {
25
+ return crypto.createHash('md5').update(fs.readFileSync(file)).digest('hex');
26
+ };
27
+ const normalizeCachePath = (file) => {
28
+ return toPosixPath(path.resolve(file));
29
+ };
30
+ const normalizeRealpath = (file) => {
31
+ return toPosixPath(fs.realpathSync.native(file));
32
+ };
33
+ const statCachedFile = (file) => {
34
+ const resolved = path.resolve(file);
35
+ if (!fs.existsSync(resolved)) {
36
+ return {
37
+ file: normalizeCachePath(file),
38
+ exists: false,
39
+ };
40
+ }
41
+ const stat = fs.statSync(resolved);
42
+ if (stat.isDirectory()) {
43
+ return {
44
+ file: normalizeCachePath(file),
45
+ exists: true,
46
+ realpath: normalizeRealpath(resolved),
47
+ type: 'directory',
48
+ mtimeMs: stat.mtimeMs,
49
+ size: stat.size,
50
+ };
51
+ }
52
+ if (!stat.isFile()) {
53
+ return {
54
+ file: normalizeCachePath(file),
55
+ exists: false,
56
+ };
57
+ }
58
+ return {
59
+ hash: hashFile(resolved),
60
+ file: normalizeCachePath(file),
61
+ exists: true,
62
+ realpath: normalizeRealpath(resolved),
63
+ type: 'file',
64
+ mtimeMs: stat.mtimeMs,
65
+ size: stat.size,
66
+ };
67
+ };
68
+ export class PersistentStyleGraphCache {
69
+ cacheRoot;
70
+ constructor(options) {
71
+ this.cacheRoot = path.join(options.root, 'node_modules', '.auklet', 'cache', 'vite-style', cacheVersion);
72
+ }
73
+ createKey(value) {
74
+ return hashValue({
75
+ cacheVersion,
76
+ value,
77
+ });
78
+ }
79
+ read(key) {
80
+ const cacheFile = this.getCacheFile(key);
81
+ if (!fs.existsSync(cacheFile))
82
+ return null;
83
+ try {
84
+ const cached = JSON.parse(fs.readFileSync(cacheFile, 'utf8'));
85
+ if (cached.key !== key)
86
+ return null;
87
+ if (!this.isFresh(cached.files))
88
+ return null;
89
+ return cached.result;
90
+ }
91
+ catch {
92
+ return null;
93
+ }
94
+ }
95
+ write(key, result, inputFiles) {
96
+ const files = Array.from(new Map(inputFiles
97
+ .map((file) => statCachedFile(file))
98
+ .map((file) => [file.file, file])).values());
99
+ try {
100
+ fs.mkdirSync(this.cacheRoot, { recursive: true });
101
+ fs.writeFileSync(this.getCacheFile(key), `${JSON.stringify({
102
+ files,
103
+ key,
104
+ result,
105
+ }, null, 2)}\n`);
106
+ }
107
+ catch {
108
+ // Cache writes are an optimization; dev CSS generation must keep working.
109
+ }
110
+ }
111
+ getCacheFile(key) {
112
+ return path.join(this.cacheRoot, `${key}.json`);
113
+ }
114
+ isFresh(files) {
115
+ for (const file of files) {
116
+ const current = statCachedFile(file.file);
117
+ if (current.exists !== file.exists)
118
+ return false;
119
+ if (!current.exists || !file.exists)
120
+ continue;
121
+ if (current.type !== file.type)
122
+ return false;
123
+ if (current.realpath !== file.realpath ||
124
+ current.size !== file.size ||
125
+ current.mtimeMs !== file.mtimeMs ||
126
+ toPosixPath(current.file) !== toPosixPath(file.file)) {
127
+ return false;
128
+ }
129
+ if (current.type === 'file' &&
130
+ file.type === 'file' &&
131
+ current.hash !== file.hash) {
132
+ return false;
133
+ }
134
+ }
135
+ return true;
136
+ }
137
+ }
@@ -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,5 +67,21 @@ export declare class ModuleStyleGraphRequestCache {
62
67
  sourceRoot: string;
63
68
  styleProcessor: StyleProcessor;
64
69
  } | null>;
70
+ getLoadResult(parsed: PackageStyleId, create: () => Promise<PackageStyleLoadResult>): Promise<PackageStyleLoadResult>;
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
+ };
65
79
  private createContext;
80
+ private createPersistentKey;
81
+ private getPersistentInputFiles;
82
+ private getSourceInputFiles;
83
+ private getResolutionInputFiles;
84
+ private getLoadResultKey;
85
+ private getLoadResultPackageName;
86
+ private invalidateLoadResults;
66
87
  }
@@ -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,6 +32,34 @@ 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
+ }
47
+ invalidatePackage(packageName) {
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;
62
+ }
27
63
  async createContext(parsed) {
28
64
  const stylePackage = this.options.packageSource
29
65
  .getPackages()
@@ -60,4 +96,93 @@ export class ModuleStyleGraphRequestCache {
60
96
  styleProcessor: packageContext.styleProcessor,
61
97
  };
62
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
+ packageRoot: normalizeFileKey(context.context.packageRoot),
107
+ parsed,
108
+ root: normalizeFileKey(this.options.root),
109
+ sourceFiles: context.packageContext.sourceFiles.map((file) => toPosixPath(path.relative(context.sourceRoot, file))),
110
+ sourceRoot: normalizeFileKey(context.sourceRoot),
111
+ });
112
+ }
113
+ getPersistentInputFiles(context, result) {
114
+ return [
115
+ ...result.watchFiles,
116
+ ...(result.cacheInputFiles ?? []),
117
+ ...this.getSourceInputFiles(context.sourceRoot, context.packageContext.sourceFiles),
118
+ ...this.getResolutionInputFiles(context.context.packageRoot),
119
+ ];
120
+ }
121
+ getSourceInputFiles(sourceRoot, sourceFiles) {
122
+ const sourceInputFiles = new Set([sourceRoot, ...sourceFiles]);
123
+ const normalizedSourceRoot = normalizeFileKey(sourceRoot);
124
+ for (const file of sourceFiles) {
125
+ let current = path.dirname(file);
126
+ while (true) {
127
+ sourceInputFiles.add(current);
128
+ if (normalizeFileKey(current) === normalizedSourceRoot)
129
+ break;
130
+ const parent = path.dirname(current);
131
+ if (parent === current)
132
+ break;
133
+ current = parent;
134
+ }
135
+ }
136
+ return Array.from(sourceInputFiles);
137
+ }
138
+ getResolutionInputFiles(packageRoot) {
139
+ const files = [path.join(packageRoot, 'package.json')];
140
+ let current = packageRoot;
141
+ const graphRoot = normalizeFileKey(this.options.root);
142
+ while (true) {
143
+ files.push(path.join(current, 'tsconfig.json'));
144
+ if (normalizeFileKey(current) === graphRoot)
145
+ break;
146
+ const parent = path.dirname(current);
147
+ if (parent === current)
148
+ break;
149
+ current = parent;
150
+ }
151
+ return files;
152
+ }
153
+ getLoadResultKey(parsed) {
154
+ return `${parsed.packageName}\0${parsed.stylePath}`;
155
+ }
156
+ getLoadResultPackageName(key) {
157
+ return key.split('\0')[0];
158
+ }
159
+ invalidateLoadResults(packageName) {
160
+ const invalidPackageNames = new Set([packageName]);
161
+ let changed = true;
162
+ while (changed) {
163
+ changed = false;
164
+ for (const [key, dependencyPackages] of this.loadResultDependencies) {
165
+ const resultPackageName = this.getLoadResultPackageName(key);
166
+ const shouldInvalidate = invalidPackageNames.has(resultPackageName) ||
167
+ Array.from(dependencyPackages).some((dependencyPackage) => invalidPackageNames.has(dependencyPackage));
168
+ if (!shouldInvalidate)
169
+ continue;
170
+ if (!invalidPackageNames.has(resultPackageName)) {
171
+ invalidPackageNames.add(resultPackageName);
172
+ changed = true;
173
+ }
174
+ }
175
+ }
176
+ for (const key of this.loadResults.keys()) {
177
+ if (!invalidPackageNames.has(this.getLoadResultPackageName(key))) {
178
+ const dependencyPackages = this.loadResultDependencies.get(key);
179
+ if (!dependencyPackages ||
180
+ !Array.from(dependencyPackages).some((dependencyPackage) => invalidPackageNames.has(dependencyPackage))) {
181
+ continue;
182
+ }
183
+ }
184
+ this.loadResults.delete(key);
185
+ this.loadResultDependencies.delete(key);
186
+ }
187
+ }
63
188
  }
@@ -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 = [];
@@ -58,7 +68,8 @@ export class StyleCodeFactory {
58
68
  const outputSpecifier = mapSpecifier(specifier);
59
69
  const parsed = this.parsePackageStyleIdInRequest(outputSpecifier, cache);
60
70
  if (parsed) {
61
- results.push(await this.createPackageStyleCode(parsed, cache));
71
+ const result = await this.createPackageStyleCode(parsed, cache);
72
+ results.push(this.withDependencyPackage(result, parsed.packageName));
62
73
  continue;
63
74
  }
64
75
  const resolvedSpecifier = toDevDependencyImportSpecifier(context, outputSpecifier);
@@ -137,7 +148,8 @@ export class StyleCodeFactory {
137
148
  const result = this.toDevModuleImportSpecifier(specifier, context, cache, sourceStyleDir);
138
149
  const parsed = this.parsePackageStyleIdInRequest(result, cache);
139
150
  if (parsed) {
140
- moduleStyleResults.push(await this.createPackageStyleCode(parsed, cache));
151
+ const loadResult = await this.createPackageStyleCode(parsed, cache);
152
+ moduleStyleResults.push(this.withDependencyPackage(loadResult, parsed.packageName));
141
153
  continue;
142
154
  }
143
155
  const resolvedSpecifier = toDevDependencyImportSpecifier(context, result);
@@ -190,4 +202,10 @@ export class StyleCodeFactory {
190
202
  parsePackageStyleIdInRequest(id, cache) {
191
203
  return parsePackageStyleId(id, cache.getPackageNames());
192
204
  }
205
+ withDependencyPackage(result, packageName) {
206
+ return {
207
+ ...result,
208
+ dependencyPackages: Array.from(new Set([packageName, ...(result.dependencyPackages ?? [])])),
209
+ };
210
+ }
193
211
  }
@@ -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;
@@ -106,6 +106,7 @@ export function aukletStylePlugin(options = {}) {
106
106
  const invalidateStyleGraph = (file) => {
107
107
  if (!graph.isSourceGraphFile(file))
108
108
  return false;
109
+ graph.invalidateFile(file);
109
110
  invalidateVirtualModules(server, graph);
110
111
  return true;
111
112
  };
@@ -116,7 +117,7 @@ export function aukletStylePlugin(options = {}) {
116
117
  };
117
118
  const handleSourceAddOrUnlink = (file) => {
118
119
  if (graph.isStyleFile(file)) {
119
- invalidateStyleGraph(file);
120
+ reloadStyleGraph(file);
120
121
  return;
121
122
  }
122
123
  reloadStyleGraph(file);
@@ -126,6 +127,10 @@ export function aukletStylePlugin(options = {}) {
126
127
  server.watcher.on('change', (file) => {
127
128
  if (graph.isStyleConfigFile(file)) {
128
129
  reloadStyleGraph(file);
130
+ return;
131
+ }
132
+ if (graph.isSourceModuleFile(file)) {
133
+ hmr.handleSourceModuleChange(server, file);
129
134
  }
130
135
  });
131
136
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "auklet",
3
- "version": "0.1.8",
3
+ "version": "0.2.0",
4
4
  "type": "module",
5
5
  "author": "chentao.arthur",
6
6
  "description": "Build utilities for TypeScript packages and module CSS output.",