import { promises, existsSync } from 'node:fs'; import process$1 from 'node:process'; 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 'node:crypto'; import presetUno from '@unocss/preset-uno'; const INCLUDE_COMMENT = "@unocss-include"; const IGNORE_COMMENT = "@unocss-ignore"; const CSS_PLACEHOLDER = "@unocss-placeholder"; const SKIP_START_COMMENT = "@unocss-skip-start"; const SKIP_END_COMMENT = "@unocss-skip-end"; const SKIP_COMMENT_RE = new RegExp(`(//\\s*?${SKIP_START_COMMENT}\\s*?|\\/\\*\\s*?${SKIP_START_COMMENT}\\s*?\\*\\/|)[\\s\\S]*?(//\\s*?${SKIP_END_COMMENT}\\s*?|\\/\\*\\s*?${SKIP_END_COMMENT}\\s*?\\*\\/|)`, "g"); const defaultPipelineExclude = [cssIdRE]; const defaultPipelineInclude = [/\.(vue|svelte|[jt]sx|mdx?|astro|elm|php|phtml|html)($|\?)/]; function deprecationCheck(config) { let warned = false; function warn(msg) { warned = true; console.warn(`[unocss] ${msg}`); } if (config.include) warn("`include` option is deprecated, use `content.pipeline.include` instead."); if (config.exclude) warn("`exclude` option is deprecated, use `content.pipeline.exclude` instead."); if (config.extraContent) warn("`extraContent` option is deprecated, use `content` instead."); if (config.content?.plain) warn("`content.plain` option is renamed to `content.inline`."); if (warned && typeof process !== "undefined" && process.env.CI) throw new Error("deprecation warning"); } function createContext(configOrPath, defaults = {}, extraConfigSources = [], resolveConfigResult = () => { }) { let root = process$1.cwd(); let rawConfig = {}; let configFileList = []; const uno = createGenerator(rawConfig, defaults); let rollupFilter = createFilter(defaultPipelineInclude, defaultPipelineExclude); 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); deprecationCheck(result.config); rawConfig = result.config; configFileList = result.sources; uno.setConfig(rawConfig); uno.config.envMode = "dev"; rollupFilter = rawConfig.content?.pipeline === false ? () => false : createFilter( rawConfig.content?.pipeline?.include || rawConfig.include || defaultPipelineInclude, rawConfig.content?.pipeline?.exclude || rawConfig.exclude || defaultPipelineExclude ); tokens.clear(); await Promise.all(modules.map((code, id) => uno.applyExtractors(code.replace(SKIP_COMMENT_RE, ""), 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.replace(SKIP_COMMENT_RE, ""), 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, get root() { return root; }, updateRoot, getConfigFileList: () => configFileList }; } function hash(str) { let i; let l; let hval = 2166136261; for (i = 0, l = str.length; i < l; i++) { hval ^= str.charCodeAt(i); hval += (hval << 1) + (hval << 4) + (hval << 7) + (hval << 8) + (hval << 24); } return `00000${(hval >>> 0).toString(36)}`.slice(-6); } 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; const skipMap = /* @__PURE__ */ new Map(); let code = original; let s = new MagicString(transformSkipCode(code, skipMap)); 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 = restoreSkipCode(s.toString(), skipMap); 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) }; } } function transformSkipCode(code, map) { for (const item of Array.from(code.matchAll(SKIP_COMMENT_RE))) { if (item != null) { const matched = item[0]; const withHashKey = `@unocss-skip-placeholder-${hash(matched)}`; map.set(withHashKey, matched); code = code.replace(matched, withHashKey); } } return code; } function restoreSkipCode(code, map) { for (const [withHashKey, matched] of map.entries()) code = code.replace(withHashKey, matched); return code; } const version = "0.57.7"; 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$1.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$1.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$1.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")); }) ); if (options.stdout && options.outFile) { consola.fatal(`Cannot use --stdout and --out-file at the same time`); return; } consola.log(green(`${name} v${version}`)); if (options.watch) consola.start("UnoCSS in watch mode..."); else consola.start("UnoCSS 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 preTransform = await transformFiles(sourceCache, "pre"); const defaultTransform = await transformFiles(preTransform); const postTransform = await transformFiles(defaultTransform, "post"); if (options2.writeTransformed) { 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).replace(SKIP_COMMENT_RE, ""))].join("\n"), { preflights: options2.preflights, minify: options2.minify } ); if (options2.stdout) { process$1.stdout.write(css); return; } const outFile = resolve(options2.cwd || process$1.cwd(), options2.outFile ?? "uno.css"); 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$1.cwd(), outFile) )} ` ); } } } export { build as b, handleError as h, resolveOptions as r, version as v };