eslint-plugin-svelte-tailwindcss
Version:
ESLint plugin for Svelte and Tailwind CSS
246 lines (243 loc) • 7.73 kB
JavaScript
import clearModule from 'clear-module';
import escalade from 'escalade/sync';
import { createJiti } from 'jiti';
import * as fs from 'node:fs/promises';
import * as path from 'node:path';
import { pathToFileURL } from 'node:url';
import postcss from 'postcss';
import postcssImport from 'postcss-import';
import { runAsWorker } from 'synckit';
import { generateRules } from 'tailwindcss/lib/lib/generateRules.js';
import { createContext } from 'tailwindcss/lib/lib/setupContextUtils.js';
import loadConfigFallback from 'tailwindcss/loadConfig.js';
import resolveConfigFallback from 'tailwindcss/resolveConfig.js';
import pkg from 'enhanced-resolve';
import fs$1 from 'node:fs';
const { CachedInputFileSystem, ResolverFactory } = pkg;
const createExpiringMap = (duration) => {
const map = /* @__PURE__ */ new Map();
return {
get(key) {
const result = map.get(key);
if (!result) {
return void 0;
}
if (result.expiration <= /* @__PURE__ */ new Date()) {
map.delete(key);
return void 0;
}
return result.value;
},
set(key, value) {
const expiration = /* @__PURE__ */ new Date();
expiration.setMilliseconds(expiration.getMilliseconds() + duration);
map.set(key, { expiration, value });
}
};
};
const fileSystem = new CachedInputFileSystem(fs$1, 3e4);
const esmResolver = ResolverFactory.createResolver({
conditionNames: ["node", "import"],
extensions: [".mjs", ".js"],
fileSystem,
mainFields: ["module"],
useSyncFileSystemCalls: true
});
const cjsResolver = ResolverFactory.createResolver({
conditionNames: ["node", "require"],
extensions: [".js", ".cjs"],
fileSystem,
mainFields: ["main"],
useSyncFileSystemCalls: true
});
const cssResolver = ResolverFactory.createResolver({
conditionNames: ["style"],
extensions: [".css"],
fileSystem,
mainFields: ["style"],
useSyncFileSystemCalls: true
});
const resolveCssFrom = (base, id) => cssResolver.resolveSync({}, base, id) || id;
const resolveJsFrom = (base, id) => {
try {
return esmResolver.resolveSync({}, base, id) || id;
} catch {
return cjsResolver.resolveSync({}, base, id) || id;
}
};
const sourceToPathMap = /* @__PURE__ */ new Map();
const sourceToEntryMap = /* @__PURE__ */ new Map();
const pathToContextMap = createExpiringMap(1e4);
const createLoader = ({
filepath,
jiti,
legacy,
onError
}) => {
const cacheKey = `${+Date.now()}`;
const loadFile = async (id, base, resourceType) => {
try {
const resolved = resolveJsFrom(base, id);
const url = pathToFileURL(resolved);
url.searchParams.append("t", cacheKey);
return await jiti.import(url.href, { default: true });
} catch (err) {
return onError(id, err, resourceType);
}
};
if (legacy) {
const baseDir = path.dirname(filepath);
return (id) => loadFile(id, baseDir, "module");
}
return async (id, base, resourceType) => ({
base,
module: await loadFile(id, base, resourceType)
});
};
const getBaseDir = (filePath) => filePath ? path.dirname(filePath) : process.cwd();
const getConfigPath = (twConfigPath, baseDir) => {
if (twConfigPath) {
if (twConfigPath.endsWith(".css")) {
return null;
}
return path.resolve(baseDir, twConfigPath);
}
try {
return escalade(baseDir, (_dir, names) => {
const configFiles = [
"tailwind.config.js",
"tailwind.config.cjs",
"tailwind.config.mjs",
"tailwind.config.ts"
];
return configFiles.find((file) => names.includes(file));
}) ?? null;
} catch {
return null;
}
};
const loadV4 = async (baseDir, pkgDir, entryPoint) => {
const pkgPath = resolveJsFrom(baseDir, "tailwindcss");
const tw = await import(pathToFileURL(pkgPath).toString());
if (!tw.__unstable__loadDesignSystem) {
return null;
}
entryPoint = entryPoint ?? `${pkgDir}/theme.css`;
const jiti = createJiti(import.meta.url, {
fsCache: false,
moduleCache: false
});
const importBasePath = path.dirname(entryPoint);
let css = await fs.readFile(entryPoint, "utf-8");
let supportsImports = false;
try {
await tw.__unstable__loadDesignSystem('@import "./empty";', {
loadStylesheet: () => {
supportsImports = true;
return { base: importBasePath, content: "" };
}
});
} catch {
}
if (!supportsImports) {
const resolveImports = postcss([postcssImport()]);
const result = await resolveImports.process(css, { from: entryPoint });
css = result.css;
}
const design = await tw.__unstable__loadDesignSystem(css, {
base: importBasePath,
loadConfig: createLoader({
filepath: entryPoint,
jiti,
legacy: true,
onError(id, err) {
console.error(`Unable to load config: ${id}`, err);
return {};
}
}),
// v4.0.0-alpha.25+
loadModule: createLoader({
filepath: entryPoint,
jiti,
legacy: false,
onError: (id, err, resourceType) => {
console.error(`Unable to load ${resourceType}: ${id}`, err);
if (resourceType === "config") {
return {};
}
return () => {
};
}
}),
// v4.0.0-alpha.24 and below
loadPlugin: createLoader({
filepath: entryPoint,
jiti,
legacy: true,
onError(id, err) {
console.error(`Unable to load plugin: ${id}`, err);
return () => {
};
}
}),
loadStylesheet: async (id, base) => {
const resolved = resolveCssFrom(base, id);
return {
base: path.dirname(resolved),
content: await fs.readFile(resolved, "utf-8")
};
}
});
return {
context: {
getClassOrder: (classList) => design.getClassOrder(classList)
},
// Stubs that are not needed for v4
generateRules: () => []
};
};
const loadTailwindConfig = async (baseDir, tailwindConfigPath, entryPoint) => {
let createContext$1 = createContext;
let generateRules$1 = generateRules;
let resolveConfig = resolveConfigFallback;
let loadConfig = loadConfigFallback;
let tailwindConfig = { content: [] };
try {
const pkgFile = resolveJsFrom(baseDir, "tailwindcss/package.json");
const pkgDir = path.dirname(pkgFile);
try {
const v4 = await loadV4(baseDir, pkgDir, entryPoint);
if (v4) {
return v4;
}
} catch {
}
resolveConfig = await import(path.join(pkgDir, "resolveConfig"));
createContext$1 = (await import(path.join(pkgDir, "lib/lib/setupContextUtils"))).createContext;
generateRules$1 = (await import(path.join(pkgDir, "lib/lib/generateRules"))).generateRules;
loadConfig = await import(path.join(pkgDir, "loadConfig"));
} catch {
}
if (tailwindConfigPath) {
clearModule(tailwindConfigPath);
const loadedConfig = loadConfig(tailwindConfigPath);
tailwindConfig = loadedConfig.default ?? loadedConfig;
}
tailwindConfig.content = ["no-op"];
return { context: createContext$1(resolveConfig(tailwindConfig)), generateRules: generateRules$1 };
};
runAsWorker(async (filePath, classes) => {
const baseDir = getBaseDir(filePath);
const configPath = sourceToPathMap.get(filePath) ?? getConfigPath(filePath, baseDir);
sourceToPathMap.set(filePath, configPath);
const entryPoint = sourceToEntryMap.get(filePath) ?? filePath;
sourceToEntryMap.set(filePath, entryPoint);
const contextKey = `${configPath}:${entryPoint}`;
const existing = pathToContextMap.get(contextKey);
if (existing) {
return existing.context.getClassOrder(classes);
}
const result = await loadTailwindConfig(baseDir, configPath, entryPoint);
pathToContextMap.set(contextKey, result);
return result.context.getClassOrder(classes);
});