import { promises, existsSync } from 'node:fs'; import { resolve, dirname, relative, normalize, basename } from 'pathe'; import fg from 'fast-glob'; import { consola } from 'consola'; import { cyan, green, dim } from 'colorette'; import { debounce } from 'perfect-debounce'; import { cssIdRE, createGenerator, BetterMap, toArray } from '@unocss/core'; import { createFilter } from '@rollup/pluginutils'; import { loadConfig } from '@unocss/config'; import MagicString from 'magic-string'; import remapping from '@ampproject/remapping'; import presetUno from '@unocss/preset-uno'; const INCLUDE_COMMENT = "@unocss-include"; const IGNORE_COMMENT = "@unocss-ignore"; const CSS_PLACEHOLDER = "@unocss-placeholder"; const defaultExclude = [cssIdRE]; const defaultInclude = [/\.(vue|svelte|[jt]sx|mdx?|astro|elm|php|phtml|html)($|\?)/]; function createContext(configOrPath, defaults = {}, extraConfigSources = [], resolveConfigResult = () => { }) { let root = process.cwd(); let rawConfig = {}; let configFileList = []; const uno = createGenerator(rawConfig, defaults); let rollupFilter = createFilter(defaultInclude, defaultExclude); const invalidations = []; const reloadListeners = []; const modules = new BetterMap(); const tokens = /* @__PURE__ */ new Set(); const tasks = []; const affectedModules = /* @__PURE__ */ new Set(); let ready = reloadConfig(); async function reloadConfig() { const result = await loadConfig(root, configOrPath, extraConfigSources, defaults); resolveConfigResult(result); rawConfig = result.config; configFileList = result.sources; uno.setConfig(rawConfig); uno.config.envMode = "dev"; rollupFilter = createFilter( rawConfig.include || defaultInclude, rawConfig.exclude || defaultExclude ); tokens.clear(); await Promise.all(modules.map((code, id) => uno.applyExtractors(code, id, tokens))); invalidate(); dispatchReload(); const presets = /* @__PURE__ */ new Set(); uno.config.presets.forEach((i) => { if (!i.name) return; if (presets.has(i.name)) console.warn(`[unocss] duplication of preset ${i.name} found, there might be something wrong with your config.`); else presets.add(i.name); }); return result; } async function updateRoot(newRoot) { if (newRoot !== root) { root = newRoot; ready = reloadConfig(); } return await ready; } function invalidate() { invalidations.forEach((cb) => cb()); } function dispatchReload() { reloadListeners.forEach((cb) => cb()); } async function extract(code, id) { if (id) modules.set(id, code); const len = tokens.size; await uno.applyExtractors(code, id, tokens); if (tokens.size > len) invalidate(); } function filter(code, id) { if (code.includes(IGNORE_COMMENT)) return false; return code.includes(INCLUDE_COMMENT) || code.includes(CSS_PLACEHOLDER) || rollupFilter(id.replace(/\?v=\w+$/, "")); } async function getConfig() { await ready; return rawConfig; } async function flushTasks() { const _tasks = [...tasks]; await Promise.all(_tasks); tasks.splice(0, _tasks.length); } return { get ready() { return ready; }, tokens, modules, affectedModules, tasks, flushTasks, invalidate, onInvalidate(fn) { invalidations.push(fn); }, filter, reloadConfig, onReload(fn) { reloadListeners.push(fn); }, uno, extract, getConfig, root, updateRoot, getConfigFileList: () => configFileList }; } async function applyTransformers(ctx, original, id, enforce = "default") { if (original.includes(IGNORE_COMMENT)) return; const transformers = (ctx.uno.config.transformers || []).filter((i) => (i.enforce || "default") === enforce); if (!transformers.length) return; let code = original; let s = new MagicString(code); const maps = []; for (const t of transformers) { if (t.idFilter) { if (!t.idFilter(id)) continue; } else if (!ctx.filter(code, id)) { continue; } await t.transform(s, id, ctx); if (s.hasChanged()) { code = s.toString(); maps.push(s.generateMap({ hires: true, source: id })); s = new MagicString(code); } } if (code !== original) { ctx.affectedModules.add(id); return { code, map: remapping(maps, () => null) }; } } const version = "0.51.13"; const defaultConfig = { envMode: "build", presets: [ presetUno() ] }; class PrettyError extends Error { constructor(message) { super(message); this.name = this.constructor.name; if (typeof Error.captureStackTrace === "function") Error.captureStackTrace(this, this.constructor); else this.stack = new Error(message).stack; } } function handleError(error) { if (error instanceof PrettyError) consola.error(error.message); process.exitCode = 1; } let watcher; async function getWatcher(options) { if (watcher && !options) return watcher; const { watch } = await import('chokidar'); const ignored = ["**/{.git,node_modules}/**"]; const newWatcher = watch(options?.patterns, { ignoreInitial: true, ignorePermissionErrors: true, ignored, cwd: options?.cwd || process.cwd() }); watcher = newWatcher; return newWatcher; } const name = "unocss"; async function resolveOptions(options) { if (!options.patterns?.length) { throw new PrettyError( `No glob patterns, try ${cyan(`${name} `)}` ); } return options; } async function build(_options) { const fileCache = /* @__PURE__ */ new Map(); const cwd = _options.cwd || process.cwd(); const options = await resolveOptions(_options); async function loadConfig() { const ctx2 = createContext(options.config, defaultConfig); const configSources2 = (await ctx2.updateRoot(cwd)).sources.map((i) => normalize(i)); return { ctx: ctx2, configSources: configSources2 }; } const { ctx, configSources } = await loadConfig(); const files = await fg(options.patterns, { cwd, absolute: true }); await Promise.all( files.map(async (file) => { fileCache.set(file, await promises.readFile(file, "utf8")); }) ); consola.log(green(`${name} v${version}`)); consola.start(`UnoCSS ${options.watch ? "in watch mode..." : "for production..."}`); const debouncedBuild = debounce( async () => { generate(options).catch(handleError); }, 100 ); const startWatcher = async () => { if (!options.watch) return; const { patterns } = options; const watcher = await getWatcher(options); if (configSources.length) watcher.add(configSources); watcher.on("all", async (type, file) => { const absolutePath = resolve(cwd, file); if (configSources.includes(absolutePath)) { await ctx.reloadConfig(); consola.info(`${cyan(basename(file))} changed, setting new config`); } else { consola.log(`${green(type)} ${dim(file)}`); if (type.startsWith("unlink")) fileCache.delete(absolutePath); else fileCache.set(absolutePath, await promises.readFile(absolutePath, "utf8")); } debouncedBuild(); }); consola.info( `Watching for changes in ${toArray(patterns).map((i) => cyan(i)).join(", ")}` ); }; await generate(options); await startWatcher().catch(handleError); function transformFiles(sources, enforce = "default") { return Promise.all( sources.map(({ id, code, transformedCode }) => new Promise((resolve2) => { applyTransformers(ctx, code, id, enforce).then((transformsRes) => { resolve2({ id, code, transformedCode: transformsRes?.code || transformedCode }); }); })) ); } async function generate(options2) { const sourceCache = Array.from(fileCache).map(([id, code]) => ({ id, code })); const outFile = resolve(options2.cwd || process.cwd(), options2.outFile ?? "uno.css"); const preTransform = await transformFiles(sourceCache, "pre"); const defaultTransform = await transformFiles(preTransform); const postTransform = await transformFiles(defaultTransform, "post"); await Promise.all( postTransform.filter(({ transformedCode }) => !!transformedCode).map(({ transformedCode, id }) => new Promise((resolve2) => { if (existsSync(id)) promises.writeFile(id, transformedCode, "utf-8").then(resolve2); })) ); const { css, matched } = await ctx.uno.generate( [...postTransform.map(({ code, transformedCode }) => transformedCode ?? code)].join("\n"), { preflights: options2.preflights, minify: options2.minify } ); const dir = dirname(outFile); if (!existsSync(dir)) await promises.mkdir(dir, { recursive: true }); await promises.writeFile(outFile, css, "utf-8"); if (!options2.watch) { consola.success( `${[...matched].length} utilities generated to ${cyan( relative(process.cwd(), outFile) )} ` ); } } } export { build as b, handleError as h, resolveOptions as r, version as v };