UNPKG

pollen-css

Version:

Utility-first CSS for the future

173 lines (153 loc) 4.13 kB
import { program } from "commander"; import { promises as fs } from "fs"; import { stringify } from "javascript-stringify"; import { lilconfig } from "lilconfig"; import mapObject from "map-obj"; import path from "path"; import * as prettier from "prettier"; import type { ConfigObject, CustomModule, PollenModule, Query, } from "../../@types/pollen"; import modules from "../modules"; import url from "url"; import { merge } from "lodash-es"; const AUTO_GENERATED_COMMENT = `/** * THIS IS AN AUTO-GENERATED FILE * Edit Pollen config to update */`; function format(css: string) { return prettier.format(css, { parser: "css" }); } /** * Formats object of properties to JSON output of CSS variables */ export function formatModule(module: PollenModule & CustomModule) { return Object.keys(module) .map((family) => { return mapObject( module[family as keyof typeof module], (key: string, value: string) => [`--${family}-${key}`, value], ); }) .reduce((acc, cur) => ({ ...acc, ...cur }), {}); } /** * Formats flat object to CSS string */ export function toCSS( selector: string, object: { [key: string]: string | number }, ) { return `${selector} ${stringify( object, (value, indent, stringify) => typeof value === "string" ? value : stringify(value), 2, )?.replace(/,\n/g, ";\n")}`; } /** * Formats query objects to CSS strings */ export function queriesToCSS( selector: string, data: { [type: string]: Query; }, ) { return `${Object.keys(data) .map((type) => { return `${Object.keys(data[type]) .map((query) => { return `@${type} ${query} { ${toCSS( selector, formatModule(data[type][query] as any), )}}`; }) .join("\n")}`; }) .join("\n")}`; } /** * Gets user config */ export async function getConfig() { const importDefault = async (filepath: string) => { const module = await import(url.pathToFileURL(filepath).href); return module.default; }; const interopRequireDefault = (obj: any) => obj && obj.__esModule ? obj : { default: obj }; // Init CLI program .option("-o, --output <path>", "output file path") .option("-c, --config <path>", "config file path"); program.parse(process.argv); // Default config const defaultConfig = { output: "./pollen.css", modules: Object.keys(modules).reduce( (acc, cur) => ({ ...acc, [cur]: true }), {}, ), }; // User config const cli = program.opts(); const configure = lilconfig("pollen", { loaders: { ".js": importDefault, ".mjs": importDefault, }, }); const configFile = cli.config ? await configure.load(cli.config) : await configure.search(); let config = interopRequireDefault(configFile?.config).default || {}; if (typeof config === "function") { config = config(modules); } return merge({}, defaultConfig, config, cli); } /** * Writes files to CSS and JSON */ export async function writeFiles(config: ConfigObject, data: PollenModule) { const output = typeof config.output === "string" ? { css: config.output, json: undefined } : config.output; const selector = config?.selector || ":root"; const writeDirIfNeeded = async (filePath: string) => { const dir = path.dirname(filePath); try { await fs.access(dir); } catch { await fs.mkdir(dir, { recursive: true }); } }; if (!output?.css) { throw new Error("No output given"); } await writeDirIfNeeded(output.css); if (output.json) { await writeDirIfNeeded(output.json); } const css = await format( `${AUTO_GENERATED_COMMENT} ${toCSS(selector, formatModule(data))} ${queriesToCSS(selector, { ...(config.media ? { media: config.media } : {}), ...(config.supports ? { supports: config.supports } : {}), })} `, ); await fs.writeFile(path.resolve(process.cwd(), output.css!), css); if (output.json) { await fs.writeFile( path.resolve(process.cwd(), output.json), JSON.stringify(data, null, 2), ); } }