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
106 lines (97 loc) • 3.07 kB
text/typescript
import { relative } from "path";
import type { LoaderContext } from "webpack";
import type { YakConfigOptions } from "../withYak/index.js";
import { resolveCrossFileConstant } from "./lib/resolveCrossFileSelectors.js";
/**
* Transform typescript to css
*
* This loader takes the cached result from the yak tsloader
* and extracts the css from the generated comments
*/
export default async function cssExtractLoader(
this: LoaderContext<YakConfigOptions>,
// Instead of the source code, we receive the extracted css
// from the yak-swc transformation
_code: string,
sourceMap: string | undefined,
): Promise<string | void> {
const callback = this.async();
// Load the module from the original typescript request (without !=! and the query)
return this.loadModule(this.resourcePath, (err, source) => {
if (err) {
return callback(err);
}
if (!source) {
return callback(
new Error(`Source code for ${this.resourcePath} is empty`),
);
}
const { experiments } = this.getOptions();
const debugLog = createDebugLogger(this, experiments?.debug);
debugLog("ts", source);
const css = extractCss(source);
debugLog("css", css);
return resolveCrossFileConstant(this, this.context, css).then((result) => {
debugLog("css resolved", css);
return callback(null, result, sourceMap);
}, callback);
});
}
function extractCss(code: string | Buffer<ArrayBufferLike>): string {
let codeString: string;
if (typeof code === "string") {
codeString = code;
} else if (code instanceof Buffer) {
codeString = code.toString("utf-8");
} else if (code instanceof ArrayBuffer) {
codeString = new TextDecoder("utf-8").decode(code);
} else {
throw new Error(
"Invalid input type: code must be string, Buffer, or ArrayBuffer",
);
}
const codeParts = codeString.split("/*YAK Extracted CSS:\n");
let result = "";
for (let i = 1; i < codeParts.length; i++) {
const codeUntilEnd = codeParts[i].split("*/")[0];
result += codeUntilEnd;
}
if (result) {
result = "/* cssmodules-pure-no-check */\n" + result;
}
return result;
}
function createDebugLogger(
loaderContext: LoaderContext<YakConfigOptions>,
debugOptions: Required<YakConfigOptions>["experiments"]["debug"],
) {
if (
!debugOptions ||
(debugOptions !== true &&
debugOptions.filter &&
!debugOptions.filter(loaderContext.resourcePath))
) {
return () => {};
}
const debugType = debugOptions === true ? "ts" : debugOptions.type;
return (
messageType: "ts" | "css" | "css resolved",
message: string | Buffer<ArrayBufferLike> | undefined,
) => {
if (messageType === debugType || debugType === "all") {
console.log(
"🐮 Yak",
messageType,
"\n",
loaderContext._compiler
? relative(
loaderContext._compiler.context,
loaderContext.resourcePath,
)
: loaderContext.resourcePath,
"\n\n",
message,
);
}
};
}