Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 39 additions & 0 deletions src/html/styles-builder/css-provider-session.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,11 @@ import {
type CSSProcessor,
CSSProcessorName,
} from "#veryfront/extensions/css/index.ts";
import {
__resetLogRecordEmitterForTests,
__subscribeLogRecordEmitter,
type LogEntry,
} from "#veryfront/utils/logger/index.ts";
import { assertEquals } from "#veryfront/testing/assert.ts";
import { afterEach, beforeEach, describe, it } from "#veryfront/testing/bdd.ts";
import {
Expand Down Expand Up @@ -203,6 +208,40 @@ describe("styles-builder CSS provider sessions", () => {
assertEquals(generated.css, "missing-optimizer|sheet|alpha");
});

it("reports a missing optimizer once, with the package that provides one", () => {
// `regenerateCSSByHash` acquires a session per request, so warning on every
// acquisition made this line the most frequent entry in a hosted project's
// logs -- once per render, at warn level, naming a registration hook with no
// documented way to act on it. State the package that registers an engine,
// and say it once while the engine stays absent.
installProcessor(createProcessor("warn-rearm"));
register(CSSOptimizationEngineName, createOptimizer("warn-rearm"));
// Observing an engine re-arms the warning, so this test does not depend on
// whether an earlier test in this file already reported the absence.
acquireCSSGenerationSession(true);
resetContracts();
installProcessor(createProcessor("warn-once"));

const records: LogEntry[] = [];
__resetLogRecordEmitterForTests();
const unsubscribe = __subscribeLogRecordEmitter((entry) => {
records.push(entry);
});
try {
acquireCSSGenerationSession(true);
acquireCSSGenerationSession(true);
acquireCSSGenerationSession(true);
} finally {
unsubscribe();
__resetLogRecordEmitterForTests();
}

const reports = records.filter((entry) => entry.message.includes("CSSOptimizationEngine"));
assertEquals(reports.length, 1);
assertEquals(reports[0]?.level, "warn");
assertEquals(reports[0]?.message.includes("@veryfront/ext-css-lightning"), true);
});

it("keeps minified and unminified output in separate cache identities", async () => {
// The reversal above is only safe because an absent optimizer is recorded
// in the pipeline identity, so an unminified entry can never be served in
Expand Down
20 changes: 18 additions & 2 deletions src/html/styles-builder/tailwind-compiler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
*/

import { tryResolve } from "#veryfront/extensions/contracts.ts";
import { getRecommendation } from "#veryfront/extensions/recommendations.ts";
import {
captureCSSOptimizationEngine,
type CSSOptimizationEngine,
Expand Down Expand Up @@ -70,6 +71,7 @@ const weakSetAdd = WeakSet.prototype.add;
const weakSetHas = WeakSet.prototype.has;
const CSS_PIPELINE_IDENTITY_SCHEMA = "veryfront.css-pipeline.v2";
const cssGenerationSessions = new WeakSet<object>();
let reportedMissingOptimizationEngine = false;
const inFlightProjectCSS = new Map<
string,
Promise<{ css: string; hash: string; fromCache: boolean }>
Expand Down Expand Up @@ -137,11 +139,25 @@ export function acquireCSSGenerationSession(minify: boolean): CSSGenerationSessi
// an engine the CSS is emitted unminified and the identity below records it
// as such, so no cache can serve a stale minified entry in its place.
const optimizationProvider = minify ? tryResolve<unknown>(CSSOptimizationEngineName) : undefined;
if (minify && optimizationProvider === undefined) {
if (optimizationProvider !== undefined) {
// Re-arm: an engine that disappears later is a new regression to report.
reportedMissingOptimizationEngine = false;
} else if (minify && !reportedMissingOptimizationEngine) {
// Warn, not debug: this is a silent quality regression for a project that
// did select an optimizer and whose registration failed. It must not be
// indistinguishable from a project that never wanted one.
logger.warn("No CSSOptimizationEngine registered; emitting unminified CSS");
//
// Once per process, not once per acquisition: regenerateCSSByHash acquires
// a session on every cold-cache request, which made this the single most
// frequent line in a hosted project's logs. Name the package that registers
// an engine, or the message asks for a hook with no way to reach it.
reportedMissingOptimizationEngine = true;
const recommendation = getRecommendation(CSSOptimizationEngineName);
logger.warn(
recommendation === undefined
? "No CSSOptimizationEngine registered; emitting unminified CSS"
: `No CSSOptimizationEngine registered; emitting unminified CSS. Install one with: deno add ${recommendation}`,
);
}
const optimizationEngine = optimizationProvider === undefined
? undefined
Expand Down