veryfront 0.1.89 → 0.1.90
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.
- package/esm/cli/commands/knowledge/command.d.ts +3 -0
- package/esm/cli/commands/knowledge/command.d.ts.map +1 -1
- package/esm/cli/commands/knowledge/command.js +27 -2
- package/esm/deno.js +1 -1
- package/esm/src/html/styles-builder/candidate-extractor.d.ts +5 -1
- package/esm/src/html/styles-builder/candidate-extractor.d.ts.map +1 -1
- package/esm/src/html/styles-builder/candidate-extractor.js +6 -1
- package/esm/src/html/styles-builder/content-version.d.ts +9 -0
- package/esm/src/html/styles-builder/content-version.d.ts.map +1 -0
- package/esm/src/html/styles-builder/content-version.js +15 -0
- package/esm/src/html/styles-builder/css-pregeneration.d.ts +11 -0
- package/esm/src/html/styles-builder/css-pregeneration.d.ts.map +1 -1
- package/esm/src/html/styles-builder/css-pregeneration.js +15 -10
- package/esm/src/html/styles-builder/prepared-project-css-cache.d.ts +31 -0
- package/esm/src/html/styles-builder/prepared-project-css-cache.d.ts.map +1 -0
- package/esm/src/html/styles-builder/prepared-project-css-cache.js +165 -0
- package/esm/src/html/styles-builder/style-scope-profile.d.ts +14 -0
- package/esm/src/html/styles-builder/style-scope-profile.d.ts.map +1 -0
- package/esm/src/html/styles-builder/style-scope-profile.js +121 -0
- package/esm/src/platform/adapters/fs/factory.d.ts.map +1 -1
- package/esm/src/platform/adapters/fs/factory.js +2 -21
- package/esm/src/platform/adapters/fs/veryfront/adapter.d.ts.map +1 -1
- package/esm/src/platform/adapters/fs/veryfront/adapter.js +44 -19
- package/esm/src/platform/adapters/fs/veryfront/default-invalidation-callbacks.d.ts +3 -0
- package/esm/src/platform/adapters/fs/veryfront/default-invalidation-callbacks.d.ts.map +1 -0
- package/esm/src/platform/adapters/fs/veryfront/default-invalidation-callbacks.js +25 -0
- package/esm/src/platform/adapters/fs/veryfront/proxy-manager.d.ts.map +1 -1
- package/esm/src/platform/adapters/fs/veryfront/proxy-manager.js +2 -15
- package/esm/src/platform/adapters/fs/veryfront/websocket-manager.d.ts +4 -0
- package/esm/src/platform/adapters/fs/veryfront/websocket-manager.d.ts.map +1 -1
- package/esm/src/platform/adapters/fs/veryfront/websocket-manager.js +2 -0
- package/esm/src/rendering/orchestrator/css-candidate-manifest.d.ts +3 -0
- package/esm/src/rendering/orchestrator/css-candidate-manifest.d.ts.map +1 -1
- package/esm/src/rendering/orchestrator/css-candidate-manifest.js +10 -5
- package/esm/src/rendering/orchestrator/html.d.ts.map +1 -1
- package/esm/src/rendering/orchestrator/html.js +8 -0
- package/esm/src/server/handlers/dev/styles-candidate-scanner.d.ts.map +1 -1
- package/esm/src/server/handlers/dev/styles-candidate-scanner.js +14 -16
- package/esm/src/server/handlers/dev/styles-css.handler.d.ts +2 -0
- package/esm/src/server/handlers/dev/styles-css.handler.d.ts.map +1 -1
- package/esm/src/server/handlers/dev/styles-css.handler.js +55 -8
- package/package.json +1 -1
- package/src/cli/commands/knowledge/command.ts +30 -2
- package/src/deno.js +1 -1
- package/src/src/html/styles-builder/candidate-extractor.ts +13 -0
- package/src/src/html/styles-builder/content-version.ts +20 -0
- package/src/src/html/styles-builder/css-pregeneration.ts +49 -12
- package/src/src/html/styles-builder/prepared-project-css-cache.ts +228 -0
- package/src/src/html/styles-builder/style-scope-profile.ts +164 -0
- package/src/src/platform/adapters/fs/factory.ts +2 -27
- package/src/src/platform/adapters/fs/veryfront/adapter.ts +49 -20
- package/src/src/platform/adapters/fs/veryfront/default-invalidation-callbacks.ts +35 -0
- package/src/src/platform/adapters/fs/veryfront/proxy-manager.ts +4 -21
- package/src/src/platform/adapters/fs/veryfront/websocket-manager.ts +3 -0
- package/src/src/rendering/orchestrator/css-candidate-manifest.ts +28 -6
- package/src/src/rendering/orchestrator/html.ts +11 -0
- package/src/src/server/handlers/dev/styles-candidate-scanner.ts +18 -15
- package/src/src/server/handlers/dev/styles-css.handler.ts +88 -16
|
@@ -245,6 +245,7 @@ export class VeryfrontFSAdapter implements FSAdapter {
|
|
|
245
245
|
},
|
|
246
246
|
clearFileListIndex: () => this.readOps.clearFileListIndex(),
|
|
247
247
|
setFileListCache: (cacheKey, files) => this.cache.setAsync(cacheKey, files),
|
|
248
|
+
pregenerateStyles: (files) => this.triggerCSSPregeneration(files),
|
|
248
249
|
});
|
|
249
250
|
|
|
250
251
|
logger.debug("Created", {
|
|
@@ -349,12 +350,10 @@ export class VeryfrontFSAdapter implements FSAdapter {
|
|
|
349
350
|
sourceFilesWithContent: fileSummary.sourceFilesWithContent,
|
|
350
351
|
});
|
|
351
352
|
|
|
352
|
-
// Trigger CSS pre-generation
|
|
353
|
-
// This
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
fileSummary.sourceFilesWithContent > 0
|
|
357
|
-
) {
|
|
353
|
+
// Trigger CSS pre-generation after the initial file snapshot is ready.
|
|
354
|
+
// This keeps stylesheet generation off the first styles request for both
|
|
355
|
+
// preview branches and published content.
|
|
356
|
+
if (fileSummary.sourceFilesWithContent > 0) {
|
|
358
357
|
this.triggerCSSPregeneration(files).catch(() => {
|
|
359
358
|
// Error already logged in triggerCSSPregeneration
|
|
360
359
|
});
|
|
@@ -445,6 +444,13 @@ export class VeryfrontFSAdapter implements FSAdapter {
|
|
|
445
444
|
|
|
446
445
|
const files = await fetchFileListForContext(this.client, warmupContext);
|
|
447
446
|
await this.cache.setAsync(effectiveCacheKey, files);
|
|
447
|
+
const fileSummary = summarizeFileList(files);
|
|
448
|
+
|
|
449
|
+
if (fileSummary.sourceFilesWithContent > 0) {
|
|
450
|
+
this.triggerCSSPregeneration(files).catch(() => {
|
|
451
|
+
// Error already logged in triggerCSSPregeneration
|
|
452
|
+
});
|
|
453
|
+
}
|
|
448
454
|
|
|
449
455
|
logger.debug("File list warmup complete", {
|
|
450
456
|
reason,
|
|
@@ -716,34 +722,57 @@ export class VeryfrontFSAdapter implements FSAdapter {
|
|
|
716
722
|
const { pregenerateCSSFromFiles, findStylesheetFromFiles } = await import(
|
|
717
723
|
"../../../../html/styles-builder/css-pregeneration.js"
|
|
718
724
|
);
|
|
725
|
+
const { resolveStyleContentVersion } = await import(
|
|
726
|
+
"../../../../html/styles-builder/content-version.js"
|
|
727
|
+
);
|
|
728
|
+
const { createStyleScopeProfile } = await import(
|
|
729
|
+
"../../../../html/styles-builder/style-scope-profile.js"
|
|
730
|
+
);
|
|
719
731
|
|
|
720
732
|
let stylesheetPath: string | undefined;
|
|
733
|
+
let styleProfile = createStyleScopeProfile();
|
|
721
734
|
const projectDir = this.normalizer.getProjectDir();
|
|
722
735
|
|
|
723
|
-
if (projectDir) {
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
}
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
736
|
+
if (!projectDir) {
|
|
737
|
+
logger.debug("Skipping CSS pre-generation without projectDir", {
|
|
738
|
+
projectSlug: this.projectSlug,
|
|
739
|
+
});
|
|
740
|
+
return;
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
try {
|
|
744
|
+
const { runtime } = await import("../../registry.js");
|
|
745
|
+
const { getConfig } = await import("../../../../config/index.js");
|
|
746
|
+
const adapter = await runtime.get();
|
|
747
|
+
const cacheKey = this.client.getProjectId() || this.projectSlug;
|
|
748
|
+
const config = await getConfig(projectDir, adapter, { cacheKey });
|
|
749
|
+
stylesheetPath = config?.tailwind?.stylesheet;
|
|
750
|
+
styleProfile = createStyleScopeProfile(config);
|
|
751
|
+
} catch (error) {
|
|
752
|
+
logger.debug("Failed to load config for CSS pre-generation", {
|
|
753
|
+
projectSlug: this.projectSlug,
|
|
754
|
+
error: error instanceof Error ? error.message : String(error),
|
|
755
|
+
});
|
|
737
756
|
}
|
|
738
757
|
|
|
739
758
|
const stylesheet = findStylesheetFromFiles(files, stylesheetPath);
|
|
759
|
+
const projectVersion = resolveStyleContentVersion(this.contentContext, {
|
|
760
|
+
branch: this.contentContext?.branch ?? null,
|
|
761
|
+
releaseId: this.contentContext?.releaseId ?? null,
|
|
762
|
+
environmentName: this.contentContext?.environmentName ?? null,
|
|
763
|
+
});
|
|
740
764
|
|
|
741
765
|
await pregenerateCSSFromFiles({
|
|
742
766
|
projectSlug: this.projectSlug,
|
|
767
|
+
projectVersion,
|
|
768
|
+
projectDir,
|
|
743
769
|
files,
|
|
770
|
+
styleProfile,
|
|
744
771
|
stylesheet,
|
|
745
772
|
stylesheetPath,
|
|
746
773
|
minify: true,
|
|
774
|
+
environment: "preview",
|
|
775
|
+
buildMode: "production",
|
|
747
776
|
});
|
|
748
777
|
} catch (error) {
|
|
749
778
|
logger.warn("CSS pre-generation failed", {
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import {
|
|
2
|
+
clearSSRModuleCache,
|
|
3
|
+
clearSSRModuleCacheForProject,
|
|
4
|
+
} from "../../../../modules/react-loader/ssr-module-loader/cache/index.js";
|
|
5
|
+
import { clearRouterDetectionCacheForProject } from "../../../../rendering/router-detection.js";
|
|
6
|
+
import {
|
|
7
|
+
clearModulePathCache,
|
|
8
|
+
invalidateModulePaths,
|
|
9
|
+
} from "../../../../transforms/mdx/esm-module-loader/cache/index.js";
|
|
10
|
+
import { clearSnippetCacheForProject } from "../../../../rendering/snippet-renderer.js";
|
|
11
|
+
import { clearRendererCacheForProject } from "../../../../rendering/renderer.js";
|
|
12
|
+
import { invalidateProjectCSS } from "../../../../html/styles-builder/tailwind-compiler.js";
|
|
13
|
+
import { invalidatePreparedProjectCSS } from "../../../../html/styles-builder/prepared-project-css-cache.js";
|
|
14
|
+
import { invalidateProjectCandidateManifests } from "../../../../rendering/orchestrator/css-candidate-manifest.js";
|
|
15
|
+
import type { InvalidationCallbacks } from "./types.js";
|
|
16
|
+
|
|
17
|
+
export function createDefaultInvalidationCallbacks(
|
|
18
|
+
callbacks?: InvalidationCallbacks,
|
|
19
|
+
): InvalidationCallbacks {
|
|
20
|
+
return {
|
|
21
|
+
clearSSRModuleCache,
|
|
22
|
+
clearModulePathCache,
|
|
23
|
+
invalidateModulePaths,
|
|
24
|
+
clearSSRModuleCacheForProject,
|
|
25
|
+
clearRouterDetectionCacheForProject,
|
|
26
|
+
clearSnippetCacheForProject,
|
|
27
|
+
clearRendererCacheForProject,
|
|
28
|
+
clearProjectCSSCache: (projectSlug: string) => {
|
|
29
|
+
invalidateProjectCSS(projectSlug);
|
|
30
|
+
invalidatePreparedProjectCSS(projectSlug);
|
|
31
|
+
invalidateProjectCandidateManifests(projectSlug);
|
|
32
|
+
},
|
|
33
|
+
...callbacks,
|
|
34
|
+
};
|
|
35
|
+
}
|
|
@@ -4,18 +4,8 @@ import { CACHE_INVARIANT_VIOLATION, INVALID_ARGUMENT } from "../../../../errors/
|
|
|
4
4
|
import { buildProxyManagerCacheKey } from "../../../../cache/index.js";
|
|
5
5
|
import { VeryfrontFSAdapter } from "./index.js";
|
|
6
6
|
import type { CacheStats, FSAdapterConfig, ResolvedContentContext } from "./types.js";
|
|
7
|
-
import {
|
|
8
|
-
clearSSRModuleCache,
|
|
9
|
-
clearSSRModuleCacheForProject,
|
|
10
|
-
} from "../../../../modules/react-loader/ssr-module-loader/cache/index.js";
|
|
11
|
-
import { clearRouterDetectionCacheForProject } from "../../../../rendering/router-detection.js";
|
|
12
|
-
import {
|
|
13
|
-
clearModulePathCache,
|
|
14
|
-
invalidateModulePaths,
|
|
15
|
-
} from "../../../../transforms/mdx/esm-module-loader/cache/index.js";
|
|
16
|
-
import { clearSnippetCacheForProject } from "../../../../rendering/snippet-renderer.js";
|
|
17
|
-
import { clearRendererCacheForProject } from "../../../../rendering/renderer.js";
|
|
18
7
|
import { GetAdapterParamsSchema } from "./schemas/index.js";
|
|
8
|
+
import { createDefaultInvalidationCallbacks } from "./default-invalidation-callbacks.js";
|
|
19
9
|
|
|
20
10
|
const logger = baseLogger.component("proxy-fs-adapter-manager");
|
|
21
11
|
|
|
@@ -306,16 +296,9 @@ export class ProxyFSAdapterManager {
|
|
|
306
296
|
projectId,
|
|
307
297
|
apiToken: effectiveToken,
|
|
308
298
|
},
|
|
309
|
-
invalidationCallbacks:
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
invalidateModulePaths,
|
|
313
|
-
clearSSRModuleCacheForProject,
|
|
314
|
-
clearRouterDetectionCacheForProject,
|
|
315
|
-
clearSnippetCacheForProject,
|
|
316
|
-
clearRendererCacheForProject,
|
|
317
|
-
...this.baseConfig.invalidationCallbacks,
|
|
318
|
-
},
|
|
299
|
+
invalidationCallbacks: createDefaultInvalidationCallbacks(
|
|
300
|
+
this.baseConfig.invalidationCallbacks,
|
|
301
|
+
),
|
|
319
302
|
};
|
|
320
303
|
|
|
321
304
|
const adapter = new VeryfrontFSAdapter(config);
|
|
@@ -43,6 +43,7 @@ interface WebSocketDeps {
|
|
|
43
43
|
cacheKey: string,
|
|
44
44
|
files: Array<{ path: string; content?: string }>,
|
|
45
45
|
) => Promise<void>;
|
|
46
|
+
pregenerateStyles?: (files: Array<{ path: string; content?: string }>) => Promise<void>;
|
|
46
47
|
}
|
|
47
48
|
|
|
48
49
|
export class WebSocketManager {
|
|
@@ -629,6 +630,7 @@ export class WebSocketManager {
|
|
|
629
630
|
const cacheKey = buildFileListCacheKey(contentContext);
|
|
630
631
|
await this.deps.setFileListCache(cacheKey, files);
|
|
631
632
|
this.deps.clearFileListIndex();
|
|
633
|
+
await this.deps.pregenerateStyles?.(files);
|
|
632
634
|
|
|
633
635
|
logger.debug("Fresh files cached (memory + Redis)", {
|
|
634
636
|
cacheKey,
|
|
@@ -771,6 +773,7 @@ export class WebSocketManager {
|
|
|
771
773
|
const cacheKey = buildFileListCacheKey(contentContext);
|
|
772
774
|
await this.deps.setFileListCache(cacheKey, files);
|
|
773
775
|
this.deps.clearFileListIndex();
|
|
776
|
+
await this.deps.pregenerateStyles?.(files);
|
|
774
777
|
|
|
775
778
|
logger.debug("FRESH FILES FETCHED", {
|
|
776
779
|
cacheKey,
|
|
@@ -1,4 +1,8 @@
|
|
|
1
1
|
import { extractCandidates } from "../../html/styles-builder/tailwind-compiler.js";
|
|
2
|
+
import {
|
|
3
|
+
filterFilesForStyleScope,
|
|
4
|
+
type StyleScopeProfile,
|
|
5
|
+
} from "../../html/styles-builder/style-scope-profile.js";
|
|
2
6
|
import { getRouteModulePaths } from "../../modules/manifest/route-module-manifest.js";
|
|
3
7
|
import { rendererLogger } from "../../utils/index.js";
|
|
4
8
|
|
|
@@ -17,6 +21,7 @@ interface RouteCandidateOptions {
|
|
|
17
21
|
projectScope: string;
|
|
18
22
|
projectVersion: string;
|
|
19
23
|
projectDir: string;
|
|
24
|
+
styleProfile?: StyleScopeProfile;
|
|
20
25
|
routeKey: string;
|
|
21
26
|
routeFilePaths: string[];
|
|
22
27
|
files: SourceFileLike[];
|
|
@@ -27,6 +32,7 @@ interface ProjectCandidateOptions {
|
|
|
27
32
|
projectScope: string;
|
|
28
33
|
projectVersion: string;
|
|
29
34
|
projectDir: string;
|
|
35
|
+
styleProfile?: StyleScopeProfile;
|
|
30
36
|
files: SourceFileLike[];
|
|
31
37
|
developmentMode: boolean;
|
|
32
38
|
}
|
|
@@ -51,8 +57,12 @@ function toRelativeProjectPath(path: string, projectDir: string): string {
|
|
|
51
57
|
return normalized.replace(/^\/+/, "");
|
|
52
58
|
}
|
|
53
59
|
|
|
54
|
-
function buildManifestCacheKey(
|
|
55
|
-
|
|
60
|
+
function buildManifestCacheKey(
|
|
61
|
+
projectScope: string,
|
|
62
|
+
projectVersion: string,
|
|
63
|
+
styleProfileHash?: string,
|
|
64
|
+
): string {
|
|
65
|
+
return `${projectScope}:${projectVersion}:${styleProfileHash ?? "default"}`;
|
|
56
66
|
}
|
|
57
67
|
|
|
58
68
|
function shouldRebuildManifest(
|
|
@@ -101,13 +111,20 @@ function buildCandidateManifest(files: SourceFileLike[], projectDir: string): Ca
|
|
|
101
111
|
function getOrBuildManifest(
|
|
102
112
|
options: Pick<
|
|
103
113
|
ProjectCandidateOptions,
|
|
104
|
-
"projectScope" | "projectVersion" | "projectDir" | "files" | "developmentMode"
|
|
114
|
+
"projectScope" | "projectVersion" | "projectDir" | "files" | "developmentMode" | "styleProfile"
|
|
105
115
|
>,
|
|
106
116
|
): CandidateManifest {
|
|
107
|
-
const manifestKey = buildManifestCacheKey(
|
|
117
|
+
const manifestKey = buildManifestCacheKey(
|
|
118
|
+
options.projectScope,
|
|
119
|
+
options.projectVersion,
|
|
120
|
+
options.styleProfile?.hash,
|
|
121
|
+
);
|
|
108
122
|
const existingManifest = manifestCache.get(manifestKey);
|
|
123
|
+
const scopedFiles = options.styleProfile
|
|
124
|
+
? filterFilesForStyleScope(options.files, options.styleProfile, options.projectDir)
|
|
125
|
+
: options.files;
|
|
109
126
|
const manifest = shouldRebuildManifest(existingManifest, options.developmentMode)
|
|
110
|
-
? buildCandidateManifest(
|
|
127
|
+
? buildCandidateManifest(scopedFiles, options.projectDir)
|
|
111
128
|
: existingManifest!;
|
|
112
129
|
|
|
113
130
|
if (manifest !== existingManifest) {
|
|
@@ -139,7 +156,11 @@ function addCandidatesForPath(
|
|
|
139
156
|
* Resolve route-scoped Tailwind candidates from a precomputed per-project manifest.
|
|
140
157
|
*/
|
|
141
158
|
export function getRouteCandidates(options: RouteCandidateOptions): Set<string> {
|
|
142
|
-
const manifestKey = buildManifestCacheKey(
|
|
159
|
+
const manifestKey = buildManifestCacheKey(
|
|
160
|
+
options.projectScope,
|
|
161
|
+
options.projectVersion,
|
|
162
|
+
options.styleProfile?.hash,
|
|
163
|
+
);
|
|
143
164
|
const manifest = getOrBuildManifest(options);
|
|
144
165
|
const routeCacheKey = `${manifestKey}:${options.routeKey}`;
|
|
145
166
|
const cachedRoute = routeCandidateCache.get(routeCacheKey);
|
|
@@ -167,6 +188,7 @@ export function getRouteCandidates(options: RouteCandidateOptions): Set<string>
|
|
|
167
188
|
logger.debug("Resolved route candidates", {
|
|
168
189
|
projectScope: options.projectScope,
|
|
169
190
|
projectVersion: options.projectVersion,
|
|
191
|
+
styleProfileHash: options.styleProfile?.hash,
|
|
170
192
|
route: options.routeKey,
|
|
171
193
|
count: routeCandidates.size,
|
|
172
194
|
});
|
|
@@ -29,6 +29,9 @@ import {
|
|
|
29
29
|
rewriteCssModuleContent,
|
|
30
30
|
} from "../../transforms/css-modules/naming.js";
|
|
31
31
|
import { getRouteCandidates } from "./css-candidate-manifest.js";
|
|
32
|
+
import { resolveStyleContentVersion } from "../../html/styles-builder/content-version.js";
|
|
33
|
+
import { createStyleScopeProfile } from "../../html/styles-builder/style-scope-profile.js";
|
|
34
|
+
import type { ResolvedContentContext } from "../../platform/adapters/fs/veryfront/types.js";
|
|
32
35
|
|
|
33
36
|
const logger = rendererLogger.component("html-generator");
|
|
34
37
|
|
|
@@ -453,9 +456,16 @@ export class HTMLGenerator {
|
|
|
453
456
|
if (typeof wrappedFs.getUnderlyingAdapter !== "function") return undefined;
|
|
454
457
|
|
|
455
458
|
const fsAdapter = wrappedFs.getUnderlyingAdapter() as {
|
|
459
|
+
getContentContext?: () => ResolvedContentContext | null;
|
|
456
460
|
getProjectData?: () => { updated_at?: string } | undefined;
|
|
457
461
|
};
|
|
458
462
|
|
|
463
|
+
const contentContext = typeof fsAdapter.getContentContext === "function"
|
|
464
|
+
? fsAdapter.getContentContext()
|
|
465
|
+
: null;
|
|
466
|
+
|
|
467
|
+
if (contentContext) return resolveStyleContentVersion(contentContext);
|
|
468
|
+
|
|
459
469
|
return fsAdapter.getProjectData?.()?.updated_at;
|
|
460
470
|
}
|
|
461
471
|
|
|
@@ -505,6 +515,7 @@ export class HTMLGenerator {
|
|
|
505
515
|
projectScope,
|
|
506
516
|
projectVersion,
|
|
507
517
|
projectDir: this.config.projectDir,
|
|
518
|
+
styleProfile: createStyleScopeProfile(this.config.config),
|
|
508
519
|
routeKey,
|
|
509
520
|
routeFilePaths,
|
|
510
521
|
files,
|
|
@@ -9,6 +9,12 @@
|
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
11
|
import { extractCandidates } from "../../../html/styles-builder/tailwind-compiler.js";
|
|
12
|
+
import { resolveStyleContentVersion } from "../../../html/styles-builder/content-version.js";
|
|
13
|
+
import {
|
|
14
|
+
createStyleScopeProfile,
|
|
15
|
+
shouldIncludeStylePath,
|
|
16
|
+
shouldTraverseStyleDirectory,
|
|
17
|
+
} from "../../../html/styles-builder/style-scope-profile.js";
|
|
12
18
|
import { serverLogger } from "../../../utils/index.js";
|
|
13
19
|
import { createFileSystem } from "../../../platform/compat/fs.js";
|
|
14
20
|
import { join } from "../../../platform/compat/path/index.js";
|
|
@@ -20,7 +26,6 @@ import { FRAMEWORK_CANDIDATES } from "./framework-candidates.generated.js";
|
|
|
20
26
|
const logger = serverLogger.component("styles-candidate-scanner");
|
|
21
27
|
|
|
22
28
|
const SOURCE_EXTENSIONS = [".tsx", ".jsx", ".mdx", ".ts", ".js"];
|
|
23
|
-
const SKIP_DIRS = new Set(["node_modules", ".cache", ".git", "dist", "build", ".vscode"]);
|
|
24
29
|
|
|
25
30
|
/** De-duplicated set of framework candidates, computed once at import time. */
|
|
26
31
|
const frameworkCandidates = new Set<string>(FRAMEWORK_CANDIDATES);
|
|
@@ -32,18 +37,6 @@ interface SourceFileProvider {
|
|
|
32
37
|
getContentContext?: () => ResolvedContentContext | null;
|
|
33
38
|
}
|
|
34
39
|
|
|
35
|
-
function resolveProjectVersion(
|
|
36
|
-
ctx: HandlerContext,
|
|
37
|
-
contentContext: ResolvedContentContext | null,
|
|
38
|
-
): string {
|
|
39
|
-
if (contentContext?.releaseId) return `release:${contentContext.releaseId}`;
|
|
40
|
-
if (contentContext?.branch) return `branch:${contentContext.branch}`;
|
|
41
|
-
if (contentContext?.environmentName) return `environment:${contentContext.environmentName}`;
|
|
42
|
-
if (ctx.releaseId) return `release:${ctx.releaseId}`;
|
|
43
|
-
if (ctx.parsedDomain?.branch) return `branch:${ctx.parsedDomain.branch}`;
|
|
44
|
-
return "live";
|
|
45
|
-
}
|
|
46
|
-
|
|
47
40
|
/**
|
|
48
41
|
* Extract Tailwind CSS candidate class names from all project source files.
|
|
49
42
|
*
|
|
@@ -52,6 +45,7 @@ function resolveProjectVersion(
|
|
|
52
45
|
* method is available (local dev mode).
|
|
53
46
|
*/
|
|
54
47
|
export async function extractProjectCandidates(ctx: HandlerContext): Promise<Set<string>> {
|
|
48
|
+
const styleProfile = createStyleScopeProfile(ctx.config);
|
|
55
49
|
const wrappedFs = ctx.adapter.fs as { getUnderlyingAdapter?: () => unknown };
|
|
56
50
|
|
|
57
51
|
if (typeof wrappedFs.getUnderlyingAdapter !== "function") {
|
|
@@ -83,8 +77,13 @@ export async function extractProjectCandidates(ctx: HandlerContext): Promise<Set
|
|
|
83
77
|
for (
|
|
84
78
|
const cls of getProjectCandidates({
|
|
85
79
|
projectScope: ctx.projectSlug ?? contentContext?.projectSlug ?? ctx.projectDir,
|
|
86
|
-
projectVersion:
|
|
80
|
+
projectVersion: resolveStyleContentVersion(contentContext, {
|
|
81
|
+
releaseId: ctx.releaseId,
|
|
82
|
+
branch: ctx.parsedDomain?.branch,
|
|
83
|
+
environmentName: ctx.environmentName,
|
|
84
|
+
}),
|
|
87
85
|
projectDir: ctx.projectDir,
|
|
86
|
+
styleProfile,
|
|
88
87
|
files,
|
|
89
88
|
developmentMode: contentContext?.sourceType === "branch",
|
|
90
89
|
})
|
|
@@ -100,6 +99,7 @@ export async function extractProjectCandidates(ctx: HandlerContext): Promise<Set
|
|
|
100
99
|
* Used in local development mode where projects are read directly from disk.
|
|
101
100
|
*/
|
|
102
101
|
async function scanLocalFiles(projectDir: string, ctx: HandlerContext): Promise<Set<string>> {
|
|
102
|
+
const styleProfile = createStyleScopeProfile(ctx.config);
|
|
103
103
|
const candidates = new Set<string>(frameworkCandidates);
|
|
104
104
|
const fs = createFileSystem();
|
|
105
105
|
|
|
@@ -116,11 +116,14 @@ async function scanLocalFiles(projectDir: string, ctx: HandlerContext): Promise<
|
|
|
116
116
|
const fullPath = join(dir, entry.name);
|
|
117
117
|
|
|
118
118
|
if (entry.isDirectory) {
|
|
119
|
-
if (
|
|
119
|
+
if (shouldTraverseStyleDirectory(styleProfile, fullPath, projectDir)) {
|
|
120
|
+
await scanDir(fullPath);
|
|
121
|
+
}
|
|
120
122
|
continue;
|
|
121
123
|
}
|
|
122
124
|
|
|
123
125
|
if (!entry.isFile) continue;
|
|
126
|
+
if (!shouldIncludeStylePath(styleProfile, fullPath, projectDir)) continue;
|
|
124
127
|
if (!SOURCE_EXTENSIONS.some((ext) => entry.name.endsWith(ext))) continue;
|
|
125
128
|
|
|
126
129
|
try {
|
|
@@ -11,20 +11,22 @@ import { BaseHandler } from "../response/base.js";
|
|
|
11
11
|
import type { HandlerContext, HandlerMetadata, HandlerPriority, HandlerResult } from "../types.js";
|
|
12
12
|
import { HTTP_OK, PRIORITY_HIGH_DEV } from "../../../utils/constants/index.js";
|
|
13
13
|
import { joinPath } from "../../../utils/path-utils.js";
|
|
14
|
-
import {
|
|
15
|
-
formatCSSError,
|
|
16
|
-
generateTailwindCSS,
|
|
17
|
-
getProjectCSS,
|
|
18
|
-
} from "../../../html/styles-builder/tailwind-compiler.js";
|
|
14
|
+
import { formatCSSError, getProjectCSS } from "../../../html/styles-builder/tailwind-compiler.js";
|
|
19
15
|
import { DEFAULT_STYLESHEET } from "../../../html/styles-builder/css-hash-cache.js";
|
|
16
|
+
import { resolveStyleContentVersion } from "../../../html/styles-builder/content-version.js";
|
|
17
|
+
import {
|
|
18
|
+
createPreparedProjectCSSContext,
|
|
19
|
+
storePreparedProjectCSS,
|
|
20
|
+
tryGetPreparedProjectCSS,
|
|
21
|
+
} from "../../../html/styles-builder/prepared-project-css-cache.js";
|
|
22
|
+
import { createStyleScopeProfile } from "../../../html/styles-builder/style-scope-profile.js";
|
|
20
23
|
import { serverLogger } from "../../../utils/index.js";
|
|
24
|
+
import type { ResolvedContentContext } from "../../../platform/adapters/fs/veryfront/types.js";
|
|
21
25
|
import { extractProjectCandidates } from "./styles-candidate-scanner.js";
|
|
22
26
|
|
|
23
27
|
const logger = serverLogger.component("styles-css-handler");
|
|
24
28
|
|
|
25
|
-
type GeneratedStylesResult =
|
|
26
|
-
| Awaited<ReturnType<typeof generateTailwindCSS>>
|
|
27
|
-
| Awaited<ReturnType<typeof getProjectCSS>>;
|
|
29
|
+
type GeneratedStylesResult = Awaited<ReturnType<typeof getProjectCSS>>;
|
|
28
30
|
|
|
29
31
|
export class StylesCSSHandler extends BaseHandler {
|
|
30
32
|
metadata: HandlerMetadata = {
|
|
@@ -40,6 +42,9 @@ export class StylesCSSHandler extends BaseHandler {
|
|
|
40
42
|
try {
|
|
41
43
|
return await this.withProxyContext(ctx, async () => {
|
|
42
44
|
const responseBuilder = this.createResponseBuilder(ctx).withCache("no-cache");
|
|
45
|
+
const projectScope = ctx.projectSlug ?? ctx.projectDir;
|
|
46
|
+
const styleProfile = createStyleScopeProfile(ctx.config);
|
|
47
|
+
const contentContext = this.getContentContext(ctx);
|
|
43
48
|
let rawCss: string;
|
|
44
49
|
try {
|
|
45
50
|
rawCss = await this.loadStylesheet(ctx);
|
|
@@ -49,6 +54,29 @@ export class StylesCSSHandler extends BaseHandler {
|
|
|
49
54
|
});
|
|
50
55
|
rawCss = DEFAULT_STYLESHEET;
|
|
51
56
|
}
|
|
57
|
+
const preparedContext = this.createPreparedCSSContext(
|
|
58
|
+
projectScope,
|
|
59
|
+
rawCss,
|
|
60
|
+
styleProfile.hash,
|
|
61
|
+
contentContext,
|
|
62
|
+
ctx,
|
|
63
|
+
);
|
|
64
|
+
|
|
65
|
+
if (preparedContext) {
|
|
66
|
+
const prepared = await tryGetPreparedProjectCSS(preparedContext);
|
|
67
|
+
if (prepared) {
|
|
68
|
+
logger.debug("Prepared CSS cache hit", {
|
|
69
|
+
projectScope,
|
|
70
|
+
projectVersion: preparedContext.projectVersion,
|
|
71
|
+
styleProfileHash: styleProfile.hash,
|
|
72
|
+
cssHash: prepared.hash,
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
return this.respond(
|
|
76
|
+
responseBuilder.withContentType("text/css; charset=utf-8", prepared.css, HTTP_OK),
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
52
80
|
|
|
53
81
|
let candidates: Set<string>;
|
|
54
82
|
try {
|
|
@@ -59,10 +87,11 @@ export class StylesCSSHandler extends BaseHandler {
|
|
|
59
87
|
});
|
|
60
88
|
candidates = new Set<string>();
|
|
61
89
|
}
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
90
|
+
let result: GeneratedStylesResult;
|
|
91
|
+
try {
|
|
92
|
+
result = await this.generateStylesheet(ctx, rawCss, candidates);
|
|
93
|
+
} catch (error) {
|
|
94
|
+
const formatted = formatCSSError(error instanceof Error ? error : String(error));
|
|
66
95
|
logger.error("Tailwind error", {
|
|
67
96
|
error: formatted.message,
|
|
68
97
|
suggestion: formatted.suggestion,
|
|
@@ -105,12 +134,20 @@ body::before {
|
|
|
105
134
|
}
|
|
106
135
|
|
|
107
136
|
logger.debug("CSS generated", {
|
|
137
|
+
projectScope,
|
|
108
138
|
candidates: candidates.size,
|
|
109
139
|
cssLength: result.css.length,
|
|
110
140
|
fromCache: "fromCache" in result ? result.fromCache : false,
|
|
111
141
|
cssHash: "hash" in result ? result.hash : undefined,
|
|
112
142
|
});
|
|
113
143
|
|
|
144
|
+
if (preparedContext && "hash" in result) {
|
|
145
|
+
await storePreparedProjectCSS(preparedContext, {
|
|
146
|
+
css: result.css,
|
|
147
|
+
hash: result.hash,
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
|
|
114
151
|
return this.respond(
|
|
115
152
|
responseBuilder.withContentType("text/css; charset=utf-8", result.css, HTTP_OK),
|
|
116
153
|
);
|
|
@@ -155,14 +192,49 @@ body::before {
|
|
|
155
192
|
rawCss: string,
|
|
156
193
|
candidates: Set<string>,
|
|
157
194
|
): Promise<GeneratedStylesResult> {
|
|
158
|
-
|
|
159
|
-
return generateTailwindCSS(rawCss, candidates);
|
|
160
|
-
}
|
|
195
|
+
const projectScope = ctx.projectSlug ?? ctx.projectDir;
|
|
161
196
|
|
|
162
|
-
return getProjectCSS(
|
|
197
|
+
return getProjectCSS(projectScope, rawCss, candidates, {
|
|
163
198
|
minify: true,
|
|
164
199
|
environment: "preview",
|
|
165
200
|
buildMode: "production",
|
|
166
201
|
});
|
|
167
202
|
}
|
|
203
|
+
|
|
204
|
+
private getContentContext(ctx: HandlerContext): ResolvedContentContext | null {
|
|
205
|
+
const wrappedFs = ctx.adapter.fs as { getUnderlyingAdapter?: () => unknown };
|
|
206
|
+
if (typeof wrappedFs.getUnderlyingAdapter !== "function") return null;
|
|
207
|
+
|
|
208
|
+
const fsAdapter = wrappedFs.getUnderlyingAdapter() as {
|
|
209
|
+
getContentContext?: () => ResolvedContentContext | null;
|
|
210
|
+
};
|
|
211
|
+
|
|
212
|
+
return typeof fsAdapter.getContentContext === "function" ? fsAdapter.getContentContext() : null;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
private createPreparedCSSContext(
|
|
216
|
+
projectScope: string | undefined,
|
|
217
|
+
rawCss: string,
|
|
218
|
+
styleProfileHash: string,
|
|
219
|
+
contentContext: ResolvedContentContext | null,
|
|
220
|
+
ctx: HandlerContext,
|
|
221
|
+
) {
|
|
222
|
+
if (!projectScope) return undefined;
|
|
223
|
+
|
|
224
|
+
return createPreparedProjectCSSContext(
|
|
225
|
+
projectScope,
|
|
226
|
+
resolveStyleContentVersion(contentContext, {
|
|
227
|
+
releaseId: ctx.releaseId,
|
|
228
|
+
branch: ctx.parsedDomain?.branch,
|
|
229
|
+
environmentName: ctx.environmentName,
|
|
230
|
+
}),
|
|
231
|
+
rawCss,
|
|
232
|
+
styleProfileHash,
|
|
233
|
+
{
|
|
234
|
+
minify: true,
|
|
235
|
+
environment: "preview",
|
|
236
|
+
buildMode: "production",
|
|
237
|
+
},
|
|
238
|
+
);
|
|
239
|
+
}
|
|
168
240
|
}
|