数通智联化工云平台
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.

306 lines
11 KiB

2 years ago
import { stat, readFile } from 'node:fs/promises';
import { normalize } from 'node:path';
import fg from 'fast-glob';
import postcss from 'postcss';
import { expandVariantGroup, notNull, regexScopePlaceholder, warnOnce, createGenerator } from '@unocss/core';
import { loadConfig } from '@unocss/config';
import { parse, generate, clone } from 'css-tree';
import MagicString from 'magic-string';
const defaultIncludeGlobs = ["**/*.{html,js,ts,jsx,tsx,vue,svelte,astro,elm,php,phtml,mdx,md}"];
async function parseApply(root, uno, directiveName) {
root.walkAtRules(directiveName, async (rule) => {
if (!rule.parent)
return;
const source = rule.source;
const classNames = expandVariantGroup(rule.params).split(/\s+/g).map((className) => className.trim().replace(/\\/, ""));
const utils = (await Promise.all(
classNames.map((i) => uno.parseToken(i, "-"))
)).filter(notNull).flat().sort((a, b) => a[0] - b[0]).sort((a, b) => (a[3] ? uno.parentOrders.get(a[3]) ?? 0 : 0) - (b[3] ? uno.parentOrders.get(b[3]) ?? 0 : 0)).reduce((acc, item) => {
const target = acc.find((i) => i[1] === item[1] && i[3] === item[3]);
if (target)
target[2] += item[2];
else
acc.push([...item]);
return acc;
}, []);
if (!utils.length)
return;
for (const i of utils) {
const [, _selector, body, parent] = i;
const selector = _selector?.replace(regexScopePlaceholder, " ") || _selector;
if (parent || selector && selector !== ".\\-") {
const node = parse(rule.parent.toString(), {
context: "rule"
});
let newSelector = generate(node.prelude);
if (selector && selector !== ".\\-") {
const selectorAST = parse(selector, {
context: "selector"
});
const prelude = clone(node.prelude);
prelude.children.forEach((child) => {
const parentSelectorAst = clone(selectorAST);
parentSelectorAst.children.forEach((i2) => {
if (i2.type === "ClassSelector" && i2.name === "\\-")
Object.assign(i2, clone(child));
});
Object.assign(child, parentSelectorAst);
});
newSelector = generate(prelude);
}
let css = `${newSelector}{${body}}`;
if (parent)
css = `${parent}{${css}}`;
const css_parsed = postcss.parse(css);
css_parsed.walkDecls((declaration) => {
declaration.source = source;
});
rule.parent.after(css_parsed);
} else {
const css = postcss.parse(body);
css.walkDecls((declaration) => {
declaration.source = source;
});
rule.parent.append(css);
}
}
rule.remove();
});
}
function themeFnRE(directiveName) {
return new RegExp(`${directiveName}\\((.*?)\\)`, "g");
}
async function parseTheme(root, uno, directiveName) {
root.walkDecls((decl) => {
const matches = Array.from(decl.value.matchAll(themeFnRE(directiveName)));
if (!matches.length)
return;
for (const match of matches) {
const rawArg = match[1].trim();
if (!rawArg)
throw new Error(`${directiveName}() expect exact one argument, but got 0`);
let value = uno.config.theme;
const keys = rawArg.slice(1, -1).split(".");
keys.every((key) => {
if (value[key] != null)
value = value[key];
else if (value[+key] != null)
value = value[+key];
else
return false;
return true;
});
if (typeof value === "string") {
const code = new MagicString(decl.value);
code.overwrite(
match.index,
match.index + match[0].length,
value
);
decl.value = code.toString();
}
}
});
}
async function parseScreen(root, uno, directiveName) {
root.walkAtRules(directiveName, async (rule) => {
let breakpointName = "";
let prefix = "";
if (rule.params)
breakpointName = rule.params.trim();
if (!breakpointName)
return;
const match = breakpointName.match(/^(?:(lt|at)-)?(\w+)$/);
if (match) {
prefix = match[1];
breakpointName = match[2];
}
const resolveBreakpoints = () => {
let breakpoints;
if (uno.userConfig && uno.userConfig.theme)
breakpoints = uno.userConfig.theme.breakpoints;
if (!breakpoints)
breakpoints = uno.config.theme.breakpoints;
return breakpoints;
};
const variantEntries = Object.entries(resolveBreakpoints() ?? {}).map(([point, size], idx) => [point, size, idx]);
const generateMediaQuery = (breakpointName2, prefix2) => {
const [, size, idx] = variantEntries.find((i) => i[0] === breakpointName2);
if (prefix2) {
if (prefix2 === "lt")
return `(max-width: ${calcMaxWidthBySize(size)})`;
else if (prefix2 === "at")
return `(min-width: ${size})${variantEntries[idx + 1] ? ` and (max-width: ${calcMaxWidthBySize(variantEntries[idx + 1][1])})` : ""}`;
else
throw new Error(`breakpoint variant not supported: ${prefix2}`);
}
return `(min-width: ${size})`;
};
if (!variantEntries.find((i) => i[0] === breakpointName))
throw new Error(`breakpoint ${breakpointName} not found`);
rule.name = "media";
rule.params = `${generateMediaQuery(breakpointName, prefix)}`;
});
}
function calcMaxWidthBySize(size) {
const value = size.match(/^-?[0-9]+\.?[0-9]*/)?.[0] || "";
const unit = size.slice(value.length);
const maxWidth = parseFloat(value) - 0.1;
return Number.isNaN(maxWidth) ? size : `${maxWidth}${unit}`;
}
function unocss(options = {}) {
warnOnce(
"`@unocss/postcss` package is in an experimental state right now. It doesn't follow semver, and may introduce breaking changes in patch versions."
);
const {
cwd = process.cwd(),
content,
configOrPath
} = options;
const directiveMap = Object.assign({
apply: "apply",
theme: "theme",
screen: "screen",
unocss: "unocss"
}, options.directiveMap || {});
const fileMap = /* @__PURE__ */ new Map();
const fileClassMap = /* @__PURE__ */ new Map();
const classes = /* @__PURE__ */ new Set();
const targetCache = /* @__PURE__ */ new Set();
const config = loadConfig(cwd, configOrPath);
let uno;
let promises = [];
let last_config_mtime = 0;
const targetRE = new RegExp(Object.values(directiveMap).join("|"));
return {
postcssPlugin: directiveMap.unocss,
plugins: [
async function(root, result) {
const from = result.opts.from?.split("?")[0];
if (!from)
return;
let isTarget = targetCache.has(from);
const isScanTarget = root.toString().includes(`@${directiveMap.unocss}`);
if (targetRE.test(root.toString())) {
if (!isTarget) {
root.walkAtRules((rule) => {
if (rule.name === directiveMap.unocss || rule.name === directiveMap.apply || rule.name === directiveMap.screen)
isTarget = true;
if (isTarget)
return false;
});
if (!isTarget) {
const themeFn = themeFnRE(directiveMap.theme);
root.walkDecls((decl) => {
if (themeFn.test(decl.value)) {
isTarget = true;
return false;
}
});
} else {
targetCache.add(from);
}
}
} else if (targetCache.has(from)) {
targetCache.delete(from);
}
if (!isTarget)
return;
try {
const cfg = await config;
if (!uno) {
uno = createGenerator(cfg.config);
} else if (cfg.sources.length) {
const config_mtime = (await stat(cfg.sources[0])).mtimeMs;
if (config_mtime > last_config_mtime) {
uno = createGenerator((await loadConfig(cwd, configOrPath)).config);
last_config_mtime = config_mtime;
}
}
} catch (error) {
throw new Error(`UnoCSS config not found: ${error.message}`);
}
const globs = content?.filter((v) => typeof v === "string") ?? defaultIncludeGlobs;
const rawContent = content?.filter((v) => typeof v === "object") ?? [];
const entries = await fg(isScanTarget ? globs : from, {
cwd,
absolute: true,
ignore: ["**/node_modules/**"],
stats: true
});
await parseApply(root, uno, directiveMap.apply);
await parseTheme(root, uno, directiveMap.theme);
await parseScreen(root, uno, directiveMap.screen);
promises.push(
...rawContent.map(async (v) => {
const { matched } = await uno.generate(v.raw, {
id: `unocss.${v.extension}`
});
for (const candidate of matched)
classes.add(candidate);
}),
...entries.map(async ({ path: file, mtimeMs }) => {
result.messages.push({
type: "dependency",
plugin: directiveMap.unocss,
file: normalize(file),
parent: from
});
if (fileMap.has(file) && mtimeMs <= fileMap.get(file))
return;
else
fileMap.set(file, mtimeMs);
const content2 = await readFile(file, "utf8");
const { matched } = await uno.generate(content2, {
id: file
});
fileClassMap.set(file, matched);
})
);
await Promise.all(promises);
promises = [];
for (const set of fileClassMap.values()) {
for (const candidate of set)
classes.add(candidate);
}
const c = await uno.generate(classes);
classes.clear();
const excludes = [];
root.walkAtRules(directiveMap.unocss, (rule) => {
if (rule.params) {
const source = rule.source;
const layers = rule.params.split(",").map((v) => v.trim());
const css = postcss.parse(
layers.map((i) => (i === "all" ? c.getLayers() : c.getLayer(i)) || "").filter(Boolean).join("\n")
);
css.walkDecls((declaration) => {
declaration.source = source;
});
rule.replaceWith(css);
excludes.push(rule.params);
}
});
root.walkAtRules(directiveMap.unocss, (rule) => {
if (!rule.params) {
const source = rule.source;
const css = postcss.parse(c.getLayers(void 0, excludes) || "");
css.walkDecls((declaration) => {
declaration.source = source;
});
rule.replaceWith(css);
}
});
}
]
};
}
unocss.postcss = true;
unocss.default = unocss;
export { unocss as default };