@twind/cli
Version:
420 lines (409 loc) • 16.7 kB
JavaScript
var __create = Object.create;
var __defProp = Object.defineProperty;
var __getProtoOf = Object.getPrototypeOf;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropSymbols = Object.getOwnPropertySymbols;
var __propIsEnum = Object.prototype.propertyIsEnumerable;
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, {enumerable: true, configurable: true, writable: true, value}) : obj[key] = value;
var __assign = (a, b) => {
for (var prop in b || (b = {}))
if (__hasOwnProp.call(b, prop))
__defNormalProp(a, prop, b[prop]);
if (__getOwnPropSymbols)
for (var prop of __getOwnPropSymbols(b)) {
if (__propIsEnum.call(b, prop))
__defNormalProp(a, prop, b[prop]);
}
return a;
};
var __markAsModule = (target) => __defProp(target, "__esModule", {value: true});
var __rest = (source, exclude) => {
var target = {};
for (var prop in source)
if (__hasOwnProp.call(source, prop) && exclude.indexOf(prop) < 0)
target[prop] = source[prop];
if (source != null && __getOwnPropSymbols)
for (var prop of __getOwnPropSymbols(source)) {
if (exclude.indexOf(prop) < 0 && __propIsEnum.call(source, prop))
target[prop] = source[prop];
}
return target;
};
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, {get: all[name], enumerable: true});
};
var __exportStar = (target, module2, desc) => {
if (module2 && typeof module2 === "object" || typeof module2 === "function") {
for (let key of __getOwnPropNames(module2))
if (!__hasOwnProp.call(target, key) && key !== "default")
__defProp(target, key, {get: () => module2[key], enumerable: !(desc = __getOwnPropDesc(module2, key)) || desc.enumerable});
}
return target;
};
var __toModule = (module2) => {
return __exportStar(__markAsModule(__defProp(module2 != null ? __create(__getProtoOf(module2)) : {}, "default", module2 && module2.__esModule && "default" in module2 ? {get: () => module2.default, enumerable: true} : {value: module2, enumerable: true})), module2);
};
// src/index.ts
__markAsModule(exports);
__export(exports, {
cli: () => cli,
run: () => run
});
// node_modules/distilt/shim-node-cjs.js
var import_url = __toModule(require("url"));
var shim_import_meta_url = (0, import_url.pathToFileURL)(__filename);
// package.json
var version = "0.2.1";
// src/cli.ts
var import_sade = __toModule(require("sade"));
// src/run.ts
var import_promises2 = __toModule(require("fs/promises"));
var import_path2 = __toModule(require("path"));
var import_kleur2 = __toModule(require("kleur"));
var import_time_span = __toModule(require("time-span"));
var import_esbuild = __toModule(require("esbuild"));
var import_twind = __toModule(require("twind"));
var import_sheets = __toModule(require("twind/sheets"));
// src/watch.ts
var import_fs = __toModule(require("fs"));
var import_path = __toModule(require("path"));
var import_chokidar = __toModule(require("chokidar"));
var import_ignore = __toModule(require("ignore"));
var import_p_event = __toModule(require("p-event"));
var import_p_debounce = __toModule(require("p-debounce"));
var import_find_up = __toModule(require("find-up"));
var readIgnoreFile = (file, cwd) => {
try {
const path3 = import_find_up.default.sync(file, {cwd});
return path3 ? import_fs.default.readFileSync(path3, {encoding: "utf-8"}) : "";
} catch {
return "";
}
};
function watch(globs, {cwd = ".", watch: watch2, ignoreFile}) {
cwd = import_path.default.resolve(cwd);
const isNotIgnored = (0, import_ignore.default)().add(ignoreFile ? readIgnoreFile(ignoreFile, cwd) : "").add(["**/.git*/**", "**/node_modules/**"]).createFilter();
const watcher = import_chokidar.default.watch(globs, {
cwd,
ignored: (file) => {
if (import_path.default.isAbsolute(file)) {
file = import_path.default.relative(cwd, file);
}
return file ? !isNotIgnored(file) : false;
},
persistent: !!watch2,
ignoreInitial: false,
alwaysStat: true,
followSymlinks: true,
awaitWriteFinish: {
stabilityThreshold: 120,
pollInterval: 20
}
});
let changed = new Map();
let isDone = false;
const nextQueue = [];
let onReady = (0, import_p_event.default)(watcher, "ready").then(async () => {
if (!watch2) {
isDone = true;
return watcher.close();
}
});
const notify = (0, import_p_debounce.default)(() => {
if (isDone || changed.size) {
const next = nextQueue.shift();
if (next) {
next.resolve(isDone && !changed.size ? {done: true, value: void 0} : {done: false, value: changed});
changed = new Map();
}
}
}, 50);
let pendingError;
watcher.on("error", (error) => {
const next = nextQueue.shift();
if (next) {
next.reject(error);
} else {
pendingError = error;
}
}).on("add", (file, stats) => {
changed.set(import_path.default.resolve(cwd, file), stats);
notify();
}).on("change", (file, stats) => {
changed.set(import_path.default.resolve(cwd, file), stats);
notify();
}).on("unlink", (file) => {
changed.set(import_path.default.resolve(cwd, file), void 0);
notify();
});
return {
[Symbol.asyncIterator]() {
return this;
},
async next() {
if (pendingError) {
throw pendingError;
}
if (onReady) {
await onReady;
onReady = null;
}
return new Promise((resolve2, reject) => {
nextQueue.push({resolve: resolve2, reject});
notify();
});
},
async return(value) {
isDone = true;
await watcher.close();
return {done: true, value};
},
async throw(error) {
isDone = true;
await watcher.close();
throw error;
}
};
}
// src/extract.ts
var import_assert = __toModule(require("assert"));
var import_promises = __toModule(require("fs/promises"));
var cleanCandidate = (candidate) => {
return candidate.replace(/^class:/, "");
};
var COMMON_INVALID_CANDIDATES = new Set([
"!DOCTYPE",
"true",
"false",
"null",
"undefined",
"class",
"className",
"currentColor"
]);
var removeInvalidCandidate = (candidate) => {
return !(COMMON_INVALID_CANDIDATES.has(candidate) || !/[a-z]/.test(candidate) || /^[A-Z]|[-/@$&]$|^\s*$/.test(candidate) || /^[@<>[][^:]+:/.test(candidate) != /^-?[<:#.,;?\d[\]%/$&@_]|^v-[^:]+:|^aria-|^https?:\/\/|^mailto:|^tel:/.test(candidate));
};
var extractRulesFromString = (content) => {
return (content.match(/(?:\[[^[]+]:|[^>"'`\s(){}\]=])[^<>"'`\s()[]*(?:\[[^[]+]|[^<>"'`\s(){}=:.,])/g) || []).map(cleanCandidate).filter(removeInvalidCandidate);
};
var extractRulesFromFile = async (file) => {
try {
return extractRulesFromString(await (0, import_promises.readFile)(file, {encoding: "utf-8"}));
} catch (error) {
return [];
}
};
// src/config.ts
var Path = __toModule(require("path"));
var import_module = __toModule(require("module"));
var import_url2 = __toModule(require("url"));
var import_locate_path = __toModule(require("locate-path"));
var import_kleur = __toModule(require("kleur"));
var TWIND_CONFIG_FILES = [
"twind.config.ts",
"twind.config.mjs",
"twind.config.js",
"twind.config.cjs"
];
var TAILWIND_CONFIG_FILES = [
"tailwind.config.ts",
"tailwind.config.mjs",
"tailwind.config.js",
"tailwind.config.cjs"
];
var findConfig = async (cwd = process.cwd()) => {
return (0, import_locate_path.default)([
...TWIND_CONFIG_FILES,
...TWIND_CONFIG_FILES.map((file) => Path.join("config", file)),
...TWIND_CONFIG_FILES.map((file) => Path.join("src", file)),
...TWIND_CONFIG_FILES.map((file) => Path.join("public", file)),
...TAILWIND_CONFIG_FILES
].map((file) => Path.resolve(cwd, file)));
};
var loadFile = (file, cwd = process.cwd()) => {
var _a, _b;
const moduleId = Path.resolve(cwd, file);
try {
const require2 = ((_b = (_a = import_module.default).createRequire) == null ? void 0 : _b.call(_a, moduleId)) || import_module.default.createRequireFromPath(moduleId);
require2("sucrase/register");
delete require2.cache[moduleId];
return require2(moduleId);
} catch (error) {
console.error(import_kleur.default.red(`Failed to to load ${import_kleur.default.bold(Path.relative(process.cwd(), moduleId))}: ${error.message}`));
return {};
}
};
var loadConfig = (configFile, cwd = process.cwd()) => {
const exports2 = loadFile(configFile, cwd);
const config = exports2.default || exports2 || {};
if (Array.isArray(config.plugins) || Object.values(config.variants || {}).some((value) => typeof value == "object") || typeof config.prefix == "string", "presets" in config || "separator" in config || "variantOrder" in config || "corePlugins" in config || "purge" in config) {
console.error(import_kleur.default.red(`${import_kleur.default.bold(Path.relative(process.cwd(), configFile))} is a tailwindcss configuration file – ${import_kleur.default.bold("only")} the theme, darkMode, purge files are used`));
return {
theme: config.theme,
darkMode: config.darkMode,
purge: config.purge
};
}
return config;
};
// src/run.ts
var run = async (globs, options = {}) => {
import_kleur2.default.enabled = !!options.color;
options.cwd = import_path2.default.resolve(options.cwd || ".");
const configFile = options.config && import_path2.default.resolve(options.cwd, options.config) || await findConfig(options.cwd);
const unknownRules = new Set();
const ignoreUnknownRules = (rule) => !unknownRules.has(rule);
const mode = {
unknown() {
},
report(info) {
if (info.id == "UNKNOWN_DIRECTIVE") {
unknownRules.add(info.rule);
}
}
};
const watched = new Map();
const candidatesByFile = new Map();
let lastCandidates = new Set([""]);
let runCount = -1;
const loadConfig2 = () => {
let config = {};
if (configFile) {
const configEndTime = (0, import_time_span.default)();
config = loadConfig(configFile, options.cwd);
console.error(import_kleur2.default.green(`Loaded configuration from ${import_kleur2.default.bold(import_path2.default.relative(process.cwd(), configFile))} in ${import_kleur2.default.bold(configEndTime.rounded() + " ms")}`));
if (runCount < 1 && config.purge) {
if (Array.isArray(config.purge)) {
globs = [...globs, ...config.purge];
} else if (Array.isArray(config.purge.content)) {
globs = [...globs, ...config.purge.content];
}
}
}
unknownRules.clear();
lastCandidates = new Set([""]);
const sheet2 = (0, import_sheets.virtualSheet)();
return {
sheet: sheet2,
tw: (0, import_twind.create)(__assign(__assign({}, config), {sheet: sheet2, mode, hash: false})).tw
};
};
let {sheet, tw} = await loadConfig2();
const outputFile = options.output && import_path2.default.resolve(options.cwd, options.output);
globs = globs.filter(Boolean);
if (!globs.length) {
globs.push("**/*.{htm,html,js,jsx,tsx,svelte,vue,mdx}");
}
console.error(import_kleur2.default.dim(`Using the following patterns: ${import_kleur2.default.bold(JSON.stringify(globs).slice(1, -1))}`));
for await (const changes of watch(configFile ? [configFile, ...globs] : globs, options)) {
runCount++;
console.error(import_kleur2.default.cyan(`Processing ${import_kleur2.default.bold(changes.size)}${options.watch ? " changed" : ""} file${changes.size == 1 ? "" : "s"}`));
const endTime = (0, import_time_span.default)();
const pendingDetections = [];
let hasChanged = false;
for (const [file, stats] of changes.entries()) {
if (file == configFile) {
if (runCount) {
;
({sheet, tw} = await loadConfig2());
hasChanged = true;
}
} else if (stats) {
const watchedStats = watched.get(file);
if (!watchedStats || watchedStats.size !== stats.size || watchedStats.mtimeMs !== stats.mtimeMs || watchedStats.ino !== stats.ino) {
pendingDetections.push(extractRulesFromFile(file).then((candidates) => {
watched.set(file, stats);
candidatesByFile.set(file, candidates);
if (!hasChanged) {
for (let index = candidates.length; index--; ) {
if (!lastCandidates.has(candidates[index])) {
hasChanged = true;
break;
}
}
}
}));
}
} else {
watched.delete(file);
candidatesByFile.delete(file);
hasChanged = true;
}
}
await Promise.all(pendingDetections);
const nextCandidates = new Set();
const addCandidate = (candidate) => {
nextCandidates.add(candidate);
};
candidatesByFile.forEach((candidates) => {
candidates.forEach(addCandidate);
});
console.error(import_kleur2.default.gray(`Extracted ${import_kleur2.default.bold(nextCandidates.size)} candidate${nextCandidates.size == 1 ? "" : "s"} from ${watched.size} file${watched.size == 1 ? "" : "s"} in ${import_kleur2.default.bold(endTime.rounded() + " ms")}`));
if (hasChanged || !equals(lastCandidates, nextCandidates)) {
const twEndTime = (0, import_time_span.default)();
sheet.reset();
tw([...nextCandidates].filter(ignoreUnknownRules).sort().join(" "));
console.error(import_kleur2.default.gray(`Generated ${import_kleur2.default.bold(sheet.target.length)} CSS rule${sheet.target.length == 1 ? "" : "s"} in ${import_kleur2.default.bold(twEndTime.rounded() + " ms")}`));
lastCandidates = nextCandidates;
let css = sheet.target.join("\n");
if (options.beautify || !options.watch) {
const cssEndTime = (0, import_time_span.default)();
const result = await (0, import_esbuild.transform)(css, {
minify: !options.beautify,
loader: "css",
sourcemap: false
});
css = result.code;
console.error(import_kleur2.default.gray(`${options.beautify ? "Beautified" : "Minimized"} CSS in ${import_kleur2.default.bold(cssEndTime.rounded() + " ms")}`));
}
if (outputFile) {
await import_promises2.default.mkdir(import_path2.default.dirname(outputFile), {recursive: true});
await import_promises2.default.writeFile(outputFile, css);
console.error(import_kleur2.default.green(`Finished ${import_kleur2.default.bold(import_path2.default.relative(process.cwd(), outputFile))} in ${import_kleur2.default.bold(endTime.rounded() + " ms")}`));
} else {
console.error(import_kleur2.default.green(`Finished in ${import_kleur2.default.bold(endTime.rounded() + " ms")}`));
console.log(css);
}
} else {
console.error(import_kleur2.default.green().dim(`No new classes detected - skipped generating CSS`));
}
if (options.watch) {
console.error("\n" + import_kleur2.default.dim("Waiting for file changes..."));
}
}
if (runCount < 0) {
console.error(import_kleur2.default.yellow(`No matching files found...`));
}
};
function equals(a, b) {
return a.size === b.size && every(b, (value) => a.has(value));
}
function every(as, predicate) {
for (const a of as) {
if (!predicate(a)) {
return false;
}
}
return true;
}
// src/cli.ts
var import_supports_color = __toModule(require("supports-color"));
var cli = (argv = process.argv) => (0, import_sade.default)("twind [...globs=**/*.{htm,html,js,jsx,tsx,svelte,vue,mdx}]", true).version(version).option("-o, --output", "Set output css file path (default print to console)").option("-c, --config", "Set config file path (default twind.config.{[cm]js,ts} or tailwind.config.{[cm]js,ts})").option("-i, --ignore", "Any file patterns to ignore").option("-I, --ignore-file", "gitignore like file", ".gitignore").option("-b, --beautify", "Generate beautified css file", false).option("-C, --cwd", "The current directory to resolve from", ".").option("-w, --watch", "Watch for changes", false).option("--color", "Print colorized output - to disable use --no-color", !!import_supports_color.default.stderr).action(async (globs, _a) => {
var {_, ["ignore-file"]: ignoreFile} = _a, options = __rest(_a, ["_", "ignore-file"]);
try {
await run([globs, ..._], __assign(__assign({}, options), {ignoreFile}));
} catch (error) {
console.error(error.stack || error.message);
process.exit(1);
}
}).parse(argv);
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
cli,
run
});
//# sourceMappingURL=cli.cjs.map