Initial commit
This commit is contained in:
commit
9843cab166
2 changed files with 74 additions and 0 deletions
62
index.ts
Normal file
62
index.ts
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
import { readdir, readFile } from "node:fs/promises";
|
||||
import { dirname, extname, join } from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { compile } from "tailwindcss";
|
||||
import type { Integration, IntegrationContext } from "muddle/contracts";
|
||||
|
||||
export interface TailwindOptions {
|
||||
route?: string;
|
||||
css?: string;
|
||||
content?: string[];
|
||||
}
|
||||
|
||||
const SCANNED_EXTENSIONS = new Set([".ts", ".tsx", ".js", ".jsx", ".html", ".spug", ".mdx"]);
|
||||
|
||||
export async function tailwindCandidates(root: string, paths?: string[]): Promise<string[]> {
|
||||
const candidates = new Set<string>();
|
||||
const visit = async (dir: string): Promise<void> => {
|
||||
for (const entry of await readdir(dir, { withFileTypes: true })) {
|
||||
if (entry.name === "node_modules" || entry.name === "dist" || entry.name.startsWith(".")) continue;
|
||||
const path = join(dir, entry.name);
|
||||
if (entry.isDirectory()) { await visit(path); continue; }
|
||||
if (!SCANNED_EXTENSIONS.has(extname(entry.name))) continue;
|
||||
if (paths && !paths.some((value) => path.includes(value))) continue;
|
||||
const source = await readFile(path, "utf8");
|
||||
for (const match of source.matchAll(/(?:class|className)\s*=\s*["'`]([^"'`]+)["'`]/g)) {
|
||||
for (const value of (match[1] ?? "").split(/\s+/)) if (value) candidates.add(value);
|
||||
}
|
||||
}
|
||||
};
|
||||
await visit(root);
|
||||
return [...candidates];
|
||||
}
|
||||
|
||||
export async function compileTailwind(css: string, candidates: string[] = []): Promise<string> {
|
||||
const tailwindCss = join(dirname(fileURLToPath(import.meta.resolve("tailwindcss/package.json"))), "index.css");
|
||||
const compiler = await compile(css, {
|
||||
base: process.cwd(),
|
||||
loadStylesheet: async (id) => {
|
||||
if (!["tailwindcss", "tailwindcss/theme.css", "tailwindcss/utilities.css"].includes(id)) throw new Error(`Unsupported Tailwind stylesheet "${id}"`);
|
||||
const path = id === "tailwindcss" ? tailwindCss : join(dirname(tailwindCss), id.split("/")[1]!);
|
||||
return { path, base: process.cwd(), content: await readFile(path, "utf8") };
|
||||
},
|
||||
});
|
||||
return compiler.build(candidates);
|
||||
}
|
||||
|
||||
export function tailwindIntegration(options: TailwindOptions = {}): Integration {
|
||||
const route = options.route ?? "/_muddle/tailwind.css";
|
||||
const source = options.css ?? '@import "tailwindcss";';
|
||||
return {
|
||||
name: "tailwind",
|
||||
async setup(context: IntegrationContext) {
|
||||
const build = () => tailwindCandidates(context.config.root, options.content).then((candidates) => compileTailwind(source, candidates));
|
||||
context.loader("tailwind", {
|
||||
name: "tailwind",
|
||||
extensions: [".tailwind.css"],
|
||||
async load(file) { return { data: {}, content: await compileTailwind(file.source, await tailwindCandidates(context.config.root, options.content)) }; },
|
||||
});
|
||||
context.route("GET", route, async () => new Response(await build(), { headers: { "content-type": "text/css; charset=utf-8", "cache-control": "public, max-age=60" } }));
|
||||
},
|
||||
};
|
||||
}
|
||||
12
package.json
Normal file
12
package.json
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"name": "@muddle/tailwind",
|
||||
"type": "module",
|
||||
"exports": "./index.ts",
|
||||
"version": "0.1.0",
|
||||
"peerDependencies": {
|
||||
"muddle": "*"
|
||||
},
|
||||
"dependencies": {
|
||||
"tailwindcss": "^4.3.3"
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue