next-yak
Version:
next-yak is a CSS-in-JS solution tailored for Next.js that seamlessly combines the expressive power of styled-components syntax with efficient build-time extraction of CSS using Next.js's built-in CSS configuration
117 lines (103 loc) • 4.17 kB
text/typescript
import { relative } from "path";
import type { YakConfigOptions } from "../../withYak/index.js";
type DebugOptions = Required<YakConfigOptions>["experiments"]["debug"];
type DebugType = NonNullable<Exclude<DebugOptions, true | undefined>>["types"] extends
| Array<infer T>
| undefined
? T
: never;
/**
* Creates a debug logger function that conditionally logs messages
* based on debug options and file paths.
*/
export function createDebugLogger(debugOptions: DebugOptions | undefined, rootPath: string) {
if (!debugOptions) {
return () => {};
}
throwOnDeprecatedDebugOptions(debugOptions);
// Handle true (log all) vs object (with optional filtering)
const pattern = debugOptions === true ? undefined : debugOptions.pattern;
const typesArray = debugOptions === true ? undefined : debugOptions.types;
// Validate and pre-compile regex pattern
let compiledPattern: RegExp | null = null;
if (pattern) {
try {
compiledPattern = new RegExp(pattern);
} catch (error) {
throw new Error(
`Invalid debug pattern: "${pattern}" is not a valid regular expression. ${
error instanceof Error ? error.message : ""
}`,
);
}
}
const types = typesArray ? new Set(typesArray) : null;
return (
messageType: DebugType,
message: string | Buffer<ArrayBufferLike> | undefined,
filePath: string,
) => {
// Filter by type if specified
if (types && !types.has(messageType)) {
return;
}
const relativePath = relative(rootPath, filePath);
// Filter by pattern if specified, or log all if no pattern
if (!compiledPattern || compiledPattern.test(relativePath)) {
console.log("🐮 Yak", `[${messageType}]`, relativePath, "\n\n", message);
}
};
}
/**
* Detects deprecated debug option shapes and throws helpful migration errors.
* TODO: Remove this function in the next major version.
*/
function throwOnDeprecatedDebugOptions(debugOptions: DebugOptions): void {
// Old API: debug: "regex-string"
if (typeof debugOptions === "string") {
const suggestion =
suggestTypesForExtensionPattern(debugOptions) ?? `debug: { pattern: "${debugOptions}" }`;
throw new Error(
`The debug option no longer accepts a string. Please update your config:\n` +
` Before: debug: "${debugOptions}"\n` +
` After: ${suggestion}`,
);
}
// Old API: debug: { filter: Function, type: string }
if (typeof debugOptions === "object" && "filter" in debugOptions) {
throw new Error(
`The debug option no longer accepts { filter, type }. Please update your config:\n` +
` Before: debug: { filter: ..., type: "..." }\n` +
` After: debug: { pattern: "...", types: ["ts", "css", "css-resolved"] }`,
);
}
// Old convention: pattern used ".css$" or ".css-resolved$" as file extension
// for type filtering — the pattern now only matches file paths
if (typeof debugOptions === "object" && debugOptions.pattern) {
const suggestion = suggestTypesForExtensionPattern(debugOptions.pattern);
if (suggestion) {
throw new Error(
`The debug pattern "${debugOptions.pattern}" looks like it's filtering by output type using the old file extension convention.\n` +
`The pattern now only matches file paths. Use the "types" option to filter by output type:\n` +
` Before: debug: { pattern: "${debugOptions.pattern}" }\n` +
` After: ${suggestion}`,
);
}
}
}
/**
* Checks if a pattern string uses the old ".css$" / ".css-resolved$" file
* extension convention for type filtering. Returns a suggested replacement
* or null if the pattern doesn't match.
*/
function suggestTypesForExtensionPattern(pattern: string): string | null {
const extensionMatch = pattern.match(/\.\(?(?:css-resolved|css)\)?\$?$/);
if (!extensionMatch) {
return null;
}
const type = extensionMatch[0].includes("css-resolved") ? "css-resolved" : "css";
const remaining = pattern.slice(0, extensionMatch.index);
return remaining
? `debug: { pattern: "${remaining}", types: ["${type}"] }`
: `debug: { types: ["${type}"] }`;
}