You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
299 lines
9.0 KiB
299 lines
9.0 KiB
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} <path/to/**/*>`)}`
|
|
);
|
|
}
|
|
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 };
|
|
|