phtml-utility-class
Version:
Write utility classes conveniently while optimising your CSS
298 lines (245 loc) • 7.94 kB
JavaScript
import phtml, { Element } from 'phtml';
import _ from 'lodash';
import { stripIndent } from 'common-tags';
import fs from 'fs-extra';
function genRegex(opts) {
let tokens = {};
if (Object(opts).regex) {
tokens = opts.regex;
} else {
tokens = {
property: /[^-\s]+/,
number: /[0-9]*\.?[0-9]+|\*/,
unit: /px|cm|mm|in|pt|pc|em|ex|ch|rem|vw|vh|vmin|vmax/,
seperator: /,/,
arg: /0*({{number}})({{unit}})?|(\w+)/,
args: /(?:({{arg}}){{seperator}}?)+/,
decl: /({{property}})(?:-({{args}}))?/
};
} // Define what a token identifier looks like <word> or {{word}}
let token = /{{(\w+)}}/gim; // Takes regex like /\d\w[0-9]<word>/ and replaces token identifier with matching token name
function replaceTokenIdent(value, tokens) {
return value.toString().replace(token, function (match, name) {
if (tokens[name]) {
if (tokens[name].toString().match(token)) {
return replaceTokenIdent(tokens[name], tokens);
}
return tokens[name].source;
} else {
return match;
}
});
} // Go through each token in object and replace tokens identifier with value
tokens = _.reduce(tokens, function (result, value, key) {
return Object.assign({}, result, {
[key]: replaceTokenIdent(value, tokens)
});
}, {}); // Create new regex for each token
return _.reduce(tokens, function (result, value, key) {
return Object.assign({}, result, {
[key]: new RegExp(value.replace(/\//g, ''), 'gmi')
});
}, {});
}
function getUtilities(str, re) {
function findMatches(regex, str, matches = []) {
const res = regex.exec(str);
res && matches.push(res) && findMatches(regex, str, matches);
return matches;
} // var declRe = new RegExp(/\b([^-\s]+)(?:-((?:(0*([0-9]*\.?[0-9]+|\*)(px|cm|mm|in|pt|pc|em|ex|ch|rem|vw|vh|vmin|vmax)?|(\w+)),?)+))?\b/, 'gi')
// let match = str.match(/\b([^-\s]+)(?:-((?:(0*([0-9]*\.?[0-9]+|\*)(px|cm|mm|in|pt|pc|em|ex|ch|rem|vw|vh|vmin|vmax)?|(\w+)),?)+))?\b/g);
const matches = findMatches(re.decl, str); // console.log(match)
let utilities = [];
for (let i = 0; i < matches.length; i++) {
let match = matches[i];
let utility = {};
if (match !== null) {
utility.class = match[1]; // console.log(re.decl)
utility.args = [];
utility.decl = match[0];
if (match[2]) {
/* Temporary fix for multiple arguments */
match[2].replace(new RegExp(re.arg, 'gmi'), function (arg) {
if (arg === '*') arg = null;
utility.args.push(arg);
});
}
if (utility.args.length === 0) {
utility.args = null;
}
utilities.push(utility);
}
}
return utilities;
}
var uniqid = require('uniqid');
const postcss = require('postcss');
const postcssrc = require('postcss-load-config'); // Get rules definitions
var rules;
if (fs.existsSync(process.cwd() + '/' + 'phtml-utility-class.config.js')) {
rules = require(process.cwd() + '/' + 'phtml-utility-class.config.js').classes; // console.log(rules)
}
function putValuesIntoArray(value) {
return Array.isArray(value) ? value : [value];
}
function genStyles(utility, acc) {
var styles = '';
if (utility.style({
rule: utility,
args: utility.args,
str: acc
}) === undefined) {
styles = '';
} else {
styles = utility.style({
rule: utility,
args: utility.args,
str: acc
});
}
return `${styles}`;
}
async function processPostCSS(src, callback) {
const ctx = {
parser: true,
map: 'inline'
};
const {
plugins,
options
} = postcssrc.sync(ctx);
const {
css
} = await postcss(plugins).process(src, {
from: undefined
});
callback(css);
}
function processInlineStyles(node, classNameID) {
const inlineStyles = node.attrs.get('style');
if (inlineStyles) {
styles = `
.${classNameID}.${classNameID} {${inlineStyles}}`;
processPostCSS(styles, css => {
// Add new array back to element
var styleTag = new Element({
name: 'style'
}, null, css); // Add new array back to element
var spanTag = new Element({
name: 'span'
}, null, styleTag);
spanTag.attrs.add({
style: 'display: none'
});
spanTag.attrs.add({
class: 'stylup-sb'
});
node.before(spanTag);
node.attrs.remove('style');
var classNames = node.attrs.get('class') ? node.attrs.get('class').split(' ') : [];
classNames.push(classNameID);
node.attrs.add({
class: classNames.join(' ')
});
});
}
}
var index = new phtml.Plugin('phtml-utility-class', opts => {
opts = opts || {};
return {
Element(node) {
if (opts.processBlockStyles) {
if (node.name === "style") {
const target = node.nodes[0];
const source = target.data;
processPostCSS(source, css => {
node.innerHTML = css;
});
}
}
var classNameID = uniqid();
if (process.env.NODE_ENV === "test") {
classNameID = 'uniqid';
} // Get styles from style attr
processInlineStyles(node, classNameID);
const hasClass = node.attrs.get('class');
if (hasClass) {
const classNames = hasClass ? node.attrs.get('class').split(' ') : null;
var re = genRegex(opts);
var utilities = getUtilities(hasClass, re);
let newClassNames = [...classNames];
let styles = [];
let hasUtilities = false;
for (let utility of utilities) {
// if (utilityClass) {
// console.log('utility class')
// }
// else {
// console.log('not utilit class')
// }
for (let rule of rules) {
rule.class = putValuesIntoArray(rule.class);
for (let property of rule.class) {
var tempRule = Object.assign({}, rule);
tempRule.class = property;
if (utility.class === tempRule.class) {
tempRule = Object.assign(tempRule, utility);
hasUtilities = true;
var output = "";
function acc(strings, ...values) {
if (!strings) {
if (typeof output !== "undefined") {
return output = output.replace(/\n$/, '');
} else {
return str;
}
} else {
let str = '';
strings.forEach((string, a) => {
str += string + (values[a] || '');
});
str = stripIndent(str);
if (typeof output !== "undefined") {
output += `${str}\n`;
}
return str;
}
}
styles.push(genStyles(tempRule, acc));
newClassNames.push(utility.class);
}
}
}
}
if (hasUtilities) {
styles = `
.${classNameID}.${classNameID} {
${styles.join('')}
}`;
processPostCSS(styles, css => {
// Add new array back to element
var styleTag = new Element({
name: 'style'
}, null, css); // Add new array back to element
var spanTag = new Element({
name: 'span'
}, null, styleTag);
spanTag.attrs.add({
style: 'display: none'
});
spanTag.attrs.add({
class: 'stylup-sb'
});
node.before(spanTag); // Add classNameID
newClassNames.push(classNameID);
node.attrs.add({
class: newClassNames.join(' ')
});
});
}
}
}
};
});
export default index;
//# sourceMappingURL=index.mjs.map