@unizap/unicss
Version:
Unicss: Build sleek interfaces straight from your markup. Fast, modern, utility-first CSS framework for rapid UI development.
180 lines (157 loc) • 5.4 kB
JavaScript
const generateCSS = require('./dist/index.js');
const glob = require('glob');
/**
* PostCSS plugin for UniCSS
* Processes @unicss directives and @apply utility classes
*
* Usage in postcss.config.js:
* module.exports = {
* plugins: {
* '@unizap/unicss': { skipBase: false },
* autoprefixer: {}
* }
* }
*
* Usage in CSS:
* @unicss base;
* @unicss utilities;
*
* Or simply:
* @unicss;
*
* @apply usage:
* .card { @apply shadow-md p-5 rounded-lg; }
*/
module.exports = (opts = {}) => {
const skipBase = opts.skipBase || false;
return {
postcssPlugin: 'unicss',
Once(root, { result }) {
const postcss = require('postcss');
// Add all source files as dependencies so PostCSS/Webpack/Vite watches them
const sourceFiles = getAllSourceFiles();
sourceFiles.forEach(file => {
result.messages.push({
type: 'dependency',
plugin: 'unicss',
file: file,
parent: result.opts.from
});
});
const hasApply = root.toString().includes('@apply');
let hasUnicssDirective = false;
let baseDirective = null;
let utilitiesDirective = null;
let fullDirective = null;
// Find @unicss directives
root.walkAtRules('unicss', (rule) => {
hasUnicssDirective = true;
const param = rule.params.trim();
if (param === 'base') {
baseDirective = rule;
} else if (param === 'utilities') {
utilitiesDirective = rule;
} else if (param === '' || param === 'all') {
fullDirective = rule;
} else {
rule.warn(result, `Unknown @unicss directive: "${param}". Use "base", "utilities", or leave empty.`);
}
});
if (!hasUnicssDirective && !hasApply) return;
// Build a processor once — reuses the same config machinery as generateCSS
// but without scanning files or producing output, so it's instant.
const processor = generateCSS.createProcessor({ skipBase });
// Process @apply rules: replace each with resolved CSS declarations
root.walkAtRules('apply', (atRule) => {
const classNames = atRule.params.trim().split(/\s+/).filter(Boolean);
const decls = [];
const unresolved = [];
for (const cls of classNames) {
// Strip variant prefixes — only base declarations are inlined
const base = cls.replace(/^(?:(?:dark:)?(?:2xl|xl|lg|md|sm):)?(?:[a-z][a-z0-9-]*:)*/, '');
const isImportant = base.startsWith('!');
const clean = isImportant ? base.slice(1) : base;
const body = processor.buildRuleForClassWithoutSelector(clean, isImportant);
if (body) {
body.split(';')
.map(d => d.trim())
.filter(Boolean)
.forEach(d => {
const colonIdx = d.indexOf(':');
if (colonIdx > 0) {
decls.push(postcss.decl({
prop: d.slice(0, colonIdx).trim(),
value: d.slice(colonIdx + 1).trim(),
}));
}
});
} else {
unresolved.push(cls);
}
}
if (unresolved.length) {
atRule.parent.insertBefore(
atRule,
postcss.comment({ text: ` @apply: unresolved classes: ${unresolved.join(' ')} ` })
);
}
atRule.replaceWith(...decls);
});
if (!hasUnicssDirective) return;
try {
const generatedCSS = generateCSS({ skipBase });
const generatedRoot = postcss.parse(generatedCSS);
if (fullDirective) {
fullDirective.replaceWith(generatedRoot.nodes);
} else {
if (baseDirective) {
const baseRoot = postcss.parse(extractBaseStyles(generatedCSS));
baseDirective.replaceWith(baseRoot.nodes);
}
if (utilitiesDirective) {
const utilitiesRoot = postcss.parse(extractUtilities(generatedCSS));
utilitiesDirective.replaceWith(utilitiesRoot.nodes);
}
}
} catch (error) {
throw root.error(`UniCSS generation failed: ${error.message}`);
}
}
};
};
module.exports.postcss = true;
/**
* Get all source files that should trigger CSS regeneration
*/
function getAllSourceFiles() {
const cwd = process.cwd();
const extensions = ['html', 'js', 'jsx', 'ts', 'tsx', 'vue', 'svelte'];
const files = [];
extensions.forEach(ext => {
const matches = glob.sync(`**/*.${ext}`, {
cwd: cwd,
absolute: true,
ignore: ['**/node_modules/**', '**/dist/**', '**/build/**', '**/.next/**']
});
files.push(...matches);
});
return files;
}
/**
* Extract base styles from generated CSS
* For now, we just return the full CSS when base is requested
* TODO: Implement intelligent splitting between base and utilities
*/
function extractBaseStyles(css) {
// Return full CSS for now - users can use @unicss without params for everything
return css;
}
/**
* Extract utility classes from generated CSS
* For now, we just return the full CSS when utilities is requested
* TODO: Implement intelligent splitting between base and utilities
*/
function extractUtilities(css) {
// Return full CSS for now - users can use @unicss without params for everything
return css;
}